Several ideas stand out:
- implement synchronous APIs
- caller adds concurrency if needed
- start goroutines only when you have concurrent work
- communicate the things you want to share, not just messages about them
Notes:
-
two principals
- Start goroutines when you have concurrent work
- share by communicating
- Asynchronous APIs
- An asynchronous API returns to the caller before its result is ready.
- not necessarily concurrent
- don’t use async callbacks
- Future
- async/await
- Go analog, function that returns channel
- Producer/consumer queue – similar to future, but receives any number of results and is typically unbuffered
- for item := range Glob(“[ab*”) …
- classical benefit – help keep single threaded apps responsive
- threads are sometimes expensive
- asynchronicity as an optimization is subtle
- examples of problems with asynchronous APIs
- A goroutine is a function executing concurrently with other goroutines in the same address space.
- Add concurrency on the caller side of the API.
- example with errgroup.WithContext
- Make concurrency an internal detail.
- you can easily see the sending and receiving sides of a channel
- Concurrency is not Asynchronicity
- Condition Variables
- Monitors
- dates to 1974
- wait and signal
- broadcast
- entire point is to put something to sleep while we wait for something to happen
- problems
- spurious wakeups
- forgotten signals, decouples signal from data
- starvation
- unresponsive cancellation
- communicate by shared memory
- Go approach is to share by communicating
- resource pool
- communicate the resource and the limits
- buffered channel can be used like a semaphore
- indicate new data
- mark transitions
- communicate the things we want to share, not just messages about them
- resource pool
- worker pool
- overhead of threads does not apply in Go, as the runtime manages that
- benefit in Go: Limit work in flight
- it’s easy to start goroutines when you need them