How to Use Mutexes in Go
Golang is King when it comes to concurrency. No other language has so many tools right out of the box, and one of those tools is the standard library’s sync.Mutex{}. Mutexes let us safely control access to data across multiple goroutines.
What problem do mutexes solve?
We don’t want multiple threads accessing the same memory at the same time. In concurrent programming, we have many different threads (or in Go, goroutines) that all potentially have access to the same variables in memory.
One case that mutexes help us avoid is the concurrent read/write problem. This occurs when one thread is writing to a variable while another variable is concurrently reading from that same variable. The program will panic because the reader could be reading bad data that is being mutated in place.
What is a mutex?
Mutex is short for mutual exclusion. Mutexes keep track of which thread has access to a variable at any given time.

Let’s see some examples! Consider the following program:
package main
import (
"fmt"
)
func main() {
m := map[int]int{}
go writeLoop(m)
go readLoop(m)
// stop program from exiting, must be killed
block := make(chan struct{})
<-block
}
func writeLoop(m map[int]int) {
for {
for i := 0; i < 100; i++ {
m[i] = i
}
}
}
func readLoop(m map[int]int) {
for {
for k, v := range m {
fmt.Println(k, "-", v)
}
}
}
The program creates a map, then starts two goroutines which each have access to that same map. One goroutine continuously mutates the values stored in the map, while the other prints the values it finds in the map.
If we run the program, we get the following output:
fatal error: concurrent map iteration and map write
In Go, it isn’t safe to read from and write to the same map at the same time.
Mutexes to the rescue
package main
import (
"fmt"
"sync"
)
func main() {
m := map[int]int{}
mux := &sync.Mutex{}
go writeLoop(m, mux)
go readLoop(m, mux)
// stop program from exiting, must be killed
block := make(chan struct{})
<-block
}
func writeLoop(m map[int]int, mux *sync.Mutex) {
for {
for i := 0; i < 100; i++ {
mux.Lock()
m[i] = i
mux.Unlock()
}
}
}
func readLoop(m map[int]int, mux *sync.Mutex) {
for {
mux.Lock()
for k, v := range m {
fmt.Println(k, "-", v)
}
mux.Unlock()
}
}
In the code above we create a sync.Mutex{} and name it mux. In the write loop, we Lock() the mutex before writing, and Unlock() it when we’re done. This ensures that no other threads can Lock() the mutex while we have it locked - those threads will block and wait until we Unlock() it.
In the reading loop we Lock() before iterating over the map, and likewise Unlock() when we’re done.
What is a read/write mutex, or RWMutex?
Maps are safe for concurrent read access, just not concurrent read/write or write/write access. A read/write mutex allows all readers to access the map at the same time, but a writer will lock out everyone else.
package main
import (
"fmt"
"sync"
)
func main() {
m := map[int]int{}
mux := &sync.RWMutex{}
go writeLoop(m, mux)
go readLoop(m, mux)
go readLoop(m, mux)
go readLoop(m, mux)
go readLoop(m, mux)
// stop program from exiting, must be killed
block := make(chan struct{})
<-block
}
func writeLoop(m map[int]int, mux *sync.RWMutex) {
for {
for i := 0; i < 100; i++ {
mux.Lock()
m[i] = i
mux.Unlock()
}
}
}
func readLoop(m map[int]int, mux *sync.RWMutex) {
for {
mux.RLock()
for k, v := range m {
fmt.Println(k, "-", v)
}
mux.RUnlock()
}
}
By using a sync.RWMutex, our program becomes more efficient. We can have as many readers as we want to access our data, but at the same time can assure that writers have exclusive access.
Related Articles
Best Practices for Interfaces in Go
Mar 15, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Interfaces in Go allow us to treat different types as the same data type temporarily because both types implement the same kind of behavior. They’re central to a Go programmer’s toolbelt and are often used improperly by new Go developers, which leads to unreadable and often buggy code.
Wrapping Errors in Go - How to Handle Nested Errors
Mar 09, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Errors in Go are a hot topic. Many newcomers to the language immediately level their first criticism, “errors in go are clunky! Let me just use try/catch!” This criticism is well-meaning but misguided.
JWT Authentication in Golang
Feb 20, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Go is becoming very popular for backend web development, and JWT’s are one of the most popular ways to handle authentication on API requests. In this article, we’ll go over the basics of JWT’s and how to implement a secure authentication strategy in Go!
BitBanged SPI in Go, An Explanation
Jan 09, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
I’m going to focus mostly on some design decisions and also how I went about writing an SPI interface using Go on a Raspberry Pi. I assume my readers have a basic understanding of what a Raspberry Pi is, and how basic electronics work. If not, read on anyway and I will be sure to include some valuable resources below.