Golang concurrency: Difference between revisions
Line 94: | Line 94: | ||
func recv(ch <-chan int) { | func recv(ch <-chan int) { | ||
for i:= range ch { | for i:= range ch { // <-- iterate over enqueued messages, as they become ready | ||
fmt.Println(i) | fmt.Println(i) | ||
} | } |
Revision as of 20:52, 6 June 2022
This page is about the methods of concurrency provided by go.
If you're looking for synchronization primitives (ex. mutexes, semaphores, ... see golang synchronization)
Not Present
Go does not:
- expose OS-threads, you only have access to it's green-threads
- abstract multiprocessing, but you could roll your own with a subprocess and IPC if you wanted to
- provide a message-queue implementation, use channels instead
Goroutines
Usage
Goroutines use green-threads rather than os-threads.
An OS thread is relatively expensive in setup and memory. One thread is reserved for a particular stack.
Go abstracts threads/threadpools with goroutines to make threads relatively cheap.func doThing() { fmt.Println("hi") } func main() { go sayHello() // <-- run in thread }Go functions default to using value objects rather than references.
Depending on your datastructure, this makes goroutines fairly concurrency-safe, since it operates on a copy of the data, rather than the same data.func printThing(a string) { fmt.Println(a) } go printThing("abc")Testing
go run -race foo.go # run, checking for race conditionsLimits
Threads are a finite resource. You only have so many CPU cores, and CPU cores can only evaluate one thread at a time. Go defaults to allowing one thread per core, but you can generally get additional performance by increasing this.
require "runtime" runtime.GOMAXPROCS(-1) // show configured max-number of threads runtime.GOMAXPROCS(2) // set max-number of threads
Channels
Channels serve as a message queue for it's goroutines.
Channels are typed, and you may optionally restrict it to direction (ex. read/write only).Create channel
ch := make(chan int) // channel (sends/recvs ints, enqueues a max of 1 int at a time) ch := make(chan int, 100) // bufferred channel (sends/recvs ints, enqueues a max of 100 ints at a time)Read/write channel
num := <- ch // read next item in channel ch <- 123 // append next item to channelChannel direction in a method signature
go func(ch <-chan int) { ... } // read-only channel go func(ch chan<- int) { ... } // write-only channel
Go tries to protect you from deadlocks.
You can iterate over enqueued messages in the channel,
but if it is impossible for your application to enqueue any more items,
and you are still looping through enqueued messages, go will panic.In order to avoid this, you must close the channel when you know you are done sending messages.
wg = sync.WaitGroup{} func recv(ch <-chan int) { for i:= range ch { // <-- iterate over enqueued messages, as they become ready fmt.Println(i) } wg.Done() } func send(ch chan<- int) { for i := 0; i < 10; i++ { ch <- i close(ch) // <-- indicate that no more items will be sent } } func main() { ch := make(ch int, 10) wg.Add(1) go send(ch) go recv(ch) wg.Wait() }