sync.Mutex
In this page:
Protecting Shared State
A sync.Mutex ensures that only one goroutine at a time can execute the code between Lock() and Unlock(), preventing concurrent goroutines from corrupting shared data by modifying it simultaneously.
Example: Protecting Shared State
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func main() {
c := &Counter{}
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Increment()
}()
}
wg.Wait()
fmt.Println(c.value)
}
Login to try C/C++/Java/PHP code in the editor
Always Pair Lock with defer Unlock
Using defer immediately after Lock() guarantees Unlock() runs no matter how the function exits -- including early returns or a panic -- which avoids permanently locking out every other goroutine.
Example: Always Pair Lock with defer Unlock
package main
import (
"fmt"
"sync"
)
type SafeMap struct {
mu sync.Mutex
data map[string]int
}
func (m *SafeMap) Set(key string, value int) {
m.mu.Lock()
defer m.mu.Unlock()
m.data[key] = value
}
func main() {
m := &SafeMap{data: make(map[string]int)}
m.Set("a", 1)
fmt.Println(m.data)
}
Login to try C/C++/Java/PHP code in the editor
What Happens Without a Mutex
Without a mutex (or other synchronization), multiple goroutines reading and writing the same variable concurrently produce a data race -- an unpredictable, sometimes silently wrong result that can differ from run to run.
Note: Run the race detector ('go run -race') on real concurrent code to catch missing synchronization.
Example: What Happens Without a Mutex
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
total := 0
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
total++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println("total:", total)
}
Login to try C/C++/Java/PHP code in the editor
- Forgetting to call Unlock() after Lock(), permanently blocking every other goroutine waiting for that mutex.
- Locking a mutex, then returning early on an error path without unlocking -- always pair Lock() with a deferred Unlock().
- Copying a struct containing a sync.Mutex by value, which duplicates the lock and breaks the mutual exclusion guarantee.
- sync.Mutex provides mutual exclusion: only one goroutine can hold the lock at a time.
- Lock() acquires the mutex (blocking if another goroutine holds it), Unlock() releases it.
- defer mu.Unlock() right after Lock() is the standard way to guarantee release, even on early returns.
- A mutex protects shared data from concurrent, unsynchronized access that would otherwise cause a data race.
Chapter Quiz — Complete all 8 topics to unlock
0/8 topics done
Complete these topics first: