Go Concurrency

November 19, 2022   

I’ve had a difficult time internalizing Go’s concurrency primitives. They’re really rather easy to understand but I get confused on basic things regularly and have to dig through many how to guides to figure things out.

Creating a Go Routine

You create a “go routine” using the go keyword. The go routine will create a thread into the go virtual machine and start executing.

A go routine can be a function or closure. There needs to be some caution used when the go routine is created as a closure as it can have sometimes unsafe access to variables outside its closure block.

Synchronization: sync.WaitGroup

To synchronize go routines typically the sync.WaitGroup type is used. The waitgroup is basically a mutex protected counter that contains three key methods

  • Add(delta) this increments (or deciments) the waitgroup counter by delta
  • Done() this decrements the waitgroup counter by 1. Its actually just a convenience wraper for Add(-1)
  • Wait() - A blocking call that won’t exit unil the waitgroup counter is 0 (like thread.Join)

Typically a pointer to a waitgroup and a reference is passed to each go routine.

Communication - channels

So go routines cannot “return” data to the main thread. So typically what is used to communicate data between go routines is the “channel” built in type.

Go routines can place data into a channel and read data out of channel via the -> and <-
operators. Channels are created via the builin make function ex: ch := make(chan int) ch is a channel of type int.

  • ch <- 10 put integer 10 into the int channel ch
  • myInt := <- ch - reach 10 out of the int channel ch

It is best to close channels with the close() function as stray channels will leak memory

On thing that confused me early on is both reading out of a channel and writing to a unbuffered channel is a blocking operation. So for every go routine that is inputting to a unbuffered channel you need another goroutine selecting the data out of the channel. Then you need a third go routine to close the channel to terminate the communication.

One fanout type system you can create a waitgroup and a channel to read and write to. One go routine will write to the channel, before spawning each go routine you will increment the wg. Typically the results will be joined in the main channel and a third go routine will close the channel while a third go routine will call wait on the wg and then close the channel.

Other Documentation and Information

There’s an excellent presentation on how concurrency can interact with the garbage collector and how to profile it with pprof here the video is rather long but its a nice simple instructional video.