Chapter 4. Concurrency Patterns in Go
We’ve explored the fundamentals of Go’s concurrency primitives and discussed how to properly use these primitives. In this chapter, we’ll do a deep-dive into how to compose these primitives into patterns that will help keep your system scalable and maintainable.
However, before we get started, we need to touch upon the format of some of the patterns contained in this chapter. In a lot of the examples, we’ll be using channels that pass empty interfaces (interface{}) around. Usage of empty interfaces in Go is controversial; however, I’ve done this for a couple of reasons. The first is that it makes it easier to write concise examples in the remainder of the book. The second is that in some cases I believe this to be more representative of what the pattern is trying to accomplish. We’ll discuss this point more directly in the section “Pipelines”.
If this is just too contentious to you, remember that you can always create Go generators for this code, and generate the patterns to utilize the type you’re interested in.
With that said, let’s dive in and learn about some patterns for concurrency in Go!
Confinement
When working with concurrent code, there are a few different options for safe operation. We’ve gone over two of them:
-
Synchronization primitives for sharing memory (e.g.,
sync.Mutex) -
Synchronization via communicating (e.g., channels)
However, there are a couple of other options that are implicitly safe within multiple concurrent processes: ...