Go Mutex and WaitGroups
A mutex is a lock that lets one goroutine use shared data at a time. A WaitGroup joins a set of goroutines before the shared result is read.
A mutex counter reaches 300
A mutex is a lock that lets one goroutine use shared data at a time. Lock takes the lock and Unlock gives it back.
package main
import (
"fmt"
"sync"
)
func addHundred(mu *sync.Mutex, wg *sync.WaitGroup, count *int) {
defer wg.Done()
for i := 0; i < 100; i++ {
mu.Lock()
*count = *count + 1
mu.Unlock()
}
}
func main() {
var mu sync.Mutex
var wg sync.WaitGroup
count := 0
for worker := 0; worker < 3; worker++ {
wg.Add(1)
go addHundred(&mu, &wg, &count)
}
wg.Wait()
fmt.Println("Count:", count)
}Each worker writes its own slot
Each worker writes only the slot with its own index. Wait holds main back until each worker has called Done, so main reads finished slots.
package main
import (
"fmt"
"sync"
)
func squareInto(id int, results []int, wg *sync.WaitGroup) {
defer wg.Done()
results[id] = (id + 1) * (id + 1)
}
func main() {
results := make([]int, 3)
var wg sync.WaitGroup
for id := 0; id < 3; id++ {
wg.Add(1)
go squareInto(id, results, &wg)
}
wg.Wait()
fmt.Println(results)
}Readers share the read lock
An RWMutex is a mutex with a read lock and a write lock. Many readers may hold the read lock together, and one writer holds the write lock alone. The writer finishes first, so every reader sees the final value.
package main
import (
"fmt"
"sync"
)
func writeScore(mu *sync.RWMutex, wg *sync.WaitGroup, score *int) {
defer wg.Done()
mu.Lock()
*score = 42
mu.Unlock()
}
func readScore(mu *sync.RWMutex, wg *sync.WaitGroup, score *int, results []int, id int) {
defer wg.Done()
mu.RLock()
results[id] = *score
mu.RUnlock()
}
func main() {
var mu sync.RWMutex
var wg sync.WaitGroup
score := 0
wg.Add(1)
go writeScore(&mu, &wg, &score)
wg.Wait()
results := make([]int, 3)
for id := 0; id < 3; id++ {
wg.Add(1)
go readScore(&mu, &wg, &score, results, id)
}
wg.Wait()
fmt.Println(results)
}Example
package main
import (
"fmt"
"sync"
)
func addPoints(mu *sync.Mutex, wg *sync.WaitGroup, score *int, points int) {
defer wg.Done()
mu.Lock()
*score += points
mu.Unlock()
}
func main() {
var mu sync.Mutex
var wg sync.WaitGroup
score := 0
amounts := []int{10, 20, 30}
for _, amount := range amounts {
wg.Add(1)
go addPoints(&mu, &wg, &score, amount)
}
wg.Wait()
fmt.Println("Score:", score)
}Key points
- A mutex is a lock that guards shared data.
- Use Lock to take the lock before touching shared data.
- Use Unlock to give the lock back after the change.
- Only one goroutine holds a mutex at a time.
- An RWMutex allows many readers at once or one writer alone.
- Wait returns after the last worker calls Done.
In this example, three goroutines each add one amount to the shared score. Each goroutine holds the lock while it adds, so no two changes overlap. Main waits for all three goroutines and then prints the score. The amounts 10, 20, and 30 add up to 60 in any order.
Note
Programs pass a mutex by pointer so all goroutines use the same lock.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Go Playground
Exercise
Why does the mutex counter reach 300 every time?