GopherCon 2018 - Rethinking Classic Concurrency Patterns
These are some notes from my experiences at the GopherCon 2018. I don’t expect these will be laid out in any particularly useful way; I am mostly taking them so I can remember some of the bits I found most useful in the future.
Concurrency Patterns
-
In Go
- Start goroutines when you have concurrent work
- Share by communicating
-
Others
- Futures
- Queues
-
Concurrency is not //-ism
-
Concurrency is not async
Async APIs
-
Callbacks
-
Futures (return object that allows later waiting)
- like single-element buffered channel
- but has traps
-
Producer/Consumer queue
- like unbuffered channel that can retrieve many results
Async Benefits
- Responsiveness
- Efficiency
Async Drawbacks
- No clarity on error conditions, cancellation, blocking vs. nonblocking, etc.
Condition Variables / Monitors
-
Wait, Signal, Broadcast for a queue
-
Drawbacks
- Spurious wakeups (too many, wrong worker)
- Forgotten signals
- Starvation
- Unresponsive cancellation
- Big Idea: issue is with sharing memory
Better Ways
-
Let the caller use concurrency, don’t build it in
- Call sites are much easier to understand
-
Share by communicating
- Send data over a channel (even a pool – buffered channel limit tokens)
Case Study: Worker Pool
-
Start a set of workers
-
Another goroutine sends tasks to worker and blocks until a worker is available
-
Benefits
- Limit work in flight
-
Drawbacks
- Can leak workers forever
- Workers can be idle arbitrarily long
- Still have resource bosts
Rethinking Worker Pools
-
Start goroutines when there is work, and exit after
- WaitGroup, acquire token in semaphore channel BEFORE
go
- Can even skip the WaitGroup in some situations – add a loop to push N sem tokens onto the semaphore channel after the work should be done
- WaitGroup, acquire token in semaphore channel BEFORE