Go uses goroutines for concurrency. Simplifying, goroutines are like threads.

Gorutines execute at the same time and share memory.

Since goroutines can read / write the same memory at the same time, you have to ensure exclusive access e.g. by using a mutex.

To coordinate work between goroutines Go provides channels, which are thread-safe queues.

Here’s an example of using worker pool of goroutines and coordinating their work with channels:

https://codeeval.dev/gist/2f155938fdd0d264618bcd8b38b091b6

There’s a lot to unpack here.

We launch 2 workers with go sqrtWorker(chIn, chOut).

Each sqrtWorker function is running independently and concurrently with all other code.

We use a single channel of int values to queue work items to be processed by worker goroutines using <- send operation on a channel.

Most of the time it’s not safe to access the same variable from multiple goroutines. Channels and sync.WaitGroup are exceptions.

Worker goroutines sqrtWorker pick up values from the channel using range.

We don’t know which worker will pick any given value.

Worker’s for loop terminates when chIn is closed with close(chIn) and worker goroutine terminates.

To pass results of worker goroutines back to the caller we use another channel.

In this example we use unbuffered channels which only have capacity for one item at a time. For that reason we launch another goroutine to fill chIn. Otherwise we would risk dead-lock.

To shutdown workers we close the chIn.

We then wait for results created by workers by iterating on chOut.

There’s one more complication. Unless we close chOut, the for sqrt := range chOut loop will wait forever.

To stop the loop, we need to close(chOut) but when to do it?