Mutexes in Go
Introduction to Mutexes
In Go, a Mutex
(short for "mutual exclusion") is a synchronization primitive used to prevent multiple goroutines from simultaneously accessing shared resources. When a goroutine locks a mutex, other goroutines are blocked from accessing the critical section until the mutex is unlocked.
Using the sync.Mutex Type
The sync.Mutex
type is used to create a mutex. The Lock()
method is used to acquire the lock, and the Unlock()
method is used to release it. Proper handling of the lock ensures that only one goroutine can access the shared resource at a time.
package main
import (
"fmt"
"sync"
)
var counter int
var mutex sync.Mutex
func increment() {
mutex.Lock()
counter++
mutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
In this example, we use a Mutex
to protect access to the shared counter
variable. The increment()
function locks the mutex before modifying the counter and unlocks it afterward. This ensures that only one goroutine can increment the counter at a time.
Deadlock
A common problem when using mutexes is deadlock, which occurs when two or more goroutines are waiting for each other to release locks, causing the program to become stuck. It's important to carefully manage how locks are acquired and released.
package main
import (
"fmt"
"sync"
)
var mutex1 sync.Mutex
var mutex2 sync.Mutex
func task1() {
mutex1.Lock()
defer mutex1.Unlock()
mutex2.Lock()
defer mutex2.Unlock()
fmt.Println("Task 1 completed")
}
func task2() {
mutex2.Lock()
defer mutex2.Unlock()
mutex1.Lock()
defer mutex1.Unlock()
fmt.Println("Task 2 completed")
}
func main() {
go task1()
go task2()
// Wait for goroutines to finish
fmt.Scanln()
}
This example introduces a potential deadlock situation. Both task1
and task2
lock two mutexes in a different order, which may lead to a situation where each goroutine is waiting for the other to release a lock, resulting in a deadlock.
Best Practices
To avoid deadlock, you should:
- Avoid locking multiple mutexes at the same time, or ensure that they are always locked in the same order across all goroutines.
- Use
defer
to ensure that mutexes are always unlocked, even in the event of an error or early return from a function. - Be mindful of the time your critical section is locked. Keep the critical section as small as possible to minimize contention between goroutines.
Conclusion
Mutexes in Go are powerful tools for synchronizing access to shared resources between multiple goroutines. By using sync.Mutex
to lock and unlock critical sections, you can ensure that your programs run safely and avoid data corruption. In the next tutorial, we will explore Go's more advanced synchronization tools, such as the sync.RWMutex
for read-write locks.