Concurrency in Go is the practice of structuring a program so multiple tasks can make progress independently, often using goroutines and channels.
Go is known for making concurrency relatively simple to write. The main building blocks are:
- Goroutines, which are lightweight concurrent functions
- Channels, which pass values between goroutines
select, which waits on multiple channel operationssync, which provides coordination primitives such as mutexes and wait groups
flowchart LR Main["main goroutine"] --> Worker["worker goroutine"] Main --> Select["select"] Worker --> Chan["channel"] Chan --> Main Chan --> Select
Goroutines
A goroutine is started by putting go in front of a function call.
package main
import (
"fmt"
"time"
)
func worker(done chan<- string) {
time.Sleep(100 * time.Millisecond)
done <- "work finished"
}
func main() {
done := make(chan string)
go worker(done)
fmt.Println(<-done)
}Goroutines are useful when you want to run tasks concurrently without managing threads directly.
Channels
Channels let goroutines communicate by sending and receiving values.
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 42
}()
value := <-ch
fmt.Println(value)
}Channels are useful for:
- Passing work between stages
- Signaling completion
- Coordinating pipelines
- Avoiding shared mutable state when possible
select
select waits on multiple channel operations and chooses one that is ready.
flowchart LR Ch1["ch1"] --> Select["select"] Ch2["ch2"] --> Select Select --> Case1["case 1"] Select --> Case2["case 2"] Select --> Default["default"]
select {
case msg := <-ch1:
fmt.Println(msg)
case ch2 <- value:
fmt.Println("sent value")
default:
fmt.Println("nothing ready")
}sync
The sync package is used when channels are not the right tool. Common types include:
sync.Mutexsync.RWMutexsync.WaitGroup
Use a mutex when multiple goroutines need safe access to shared data. Use a wait group when you want to wait for several goroutines to finish.
Tradeoffs
Go makes concurrency ergonomic, but concurrency is still easy to get wrong.
Common problems include:
- Race conditions
- Deadlocks
- Goroutine leaks
- Blocking on channels that nobody reads from
Rule of thumb:
- Use goroutines to do work concurrently
- Use channels to communicate
- Use mutexes when shared state is unavoidable