Advanced Concurrency: sync.Mutex, RWMutex, WaitGroup
What Is Concurrency in Go?
Concurrency is like having multiple workers in a kitchen. Instead of one chef doing all the tasks, multiple chefs work together to prepare a meal faster. In programming, concurrency allows multiple tasks to run at the same time, making programs more efficient.
Why Do We Need Synchronization?
Imagine two chefs trying to use the same knife at the same time. Chaos, right? Similarly, when multiple parts of a program (goroutines) try to access the same data simultaneously, it can lead to errors. That’s where synchronization tools like sync.Mutex
, RWMutex
, and WaitGroup
come in.
Using sync.Mutex to Lock Data
A Mutex
(short for "mutual exclusion") acts like a lock. Only one goroutine can hold the lock at a time. Here’s a simple example:
package main
import (
"fmt"
"sync"
)
var counter int
var mu sync.Mutex
func increment() {
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Final counter:", counter)
}
In this example, the Mutex
ensures that only one goroutine updates the counter
at a time.
RWMutex: When Reading Is More Common Than Writing
Sometimes, multiple goroutines only need to read data, not write it. A RWMutex
(Read-Write Mutex) allows multiple readers or one writer at a time. Think of it like a library—many people can read books, but only one can update the catalog.
var rwmu sync.RWMutex
var data map[string]string
func readData(key string) string {
rwmu.RLock()
defer rwmu.RUnlock()
return data[key]
}
func writeData(key, value string) {
rwmu.Lock()
defer rwmu.Unlock()
data[key] = value
}
WaitGroup: Waiting for Goroutines to Finish
A WaitGroup
is like a parent waiting for all their kids to come home before dinner. It ensures all goroutines complete before moving forward.
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Worker %d done\n", id)
}(i)
}
wg.Wait()
fmt.Println("All workers finished")
}
Real-World Use Case: Web Server Request Counter
Let’s say you’re building a web server that counts requests. Multiple users may hit the server at the same time, so you need thread-safe counting.
package main
import (
"fmt"
"net/http"
"sync"
)
var requestCount int
var mu sync.Mutex
func handler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
requestCount++
mu.Unlock()
fmt.Fprintf(w, "Total requests: %d", requestCount)
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
Here, the Mutex
prevents race conditions when updating requestCount
.
Summary
- Mutex: Locks data for exclusive access (one writer at a time).
- RWMutex: Allows multiple readers or one writer.
- WaitGroup: Waits for goroutines to finish before proceeding.
These tools help manage concurrency safely in Go programs.