🔐 Go Synchronization Primitives: The Traffic Control Story
Imagine a busy city intersection. Cars (goroutines) are zooming everywhere. Without traffic lights and rules, there would be chaos and crashes. Go’s synchronization primitives are like the traffic control system—they keep everything safe and orderly!
🚗 The Big Picture: Why Do We Need Synchronization?
When multiple goroutines (like little workers) access the same data at the same time, bad things can happen. It’s like two kids trying to write on the same piece of paper at once—the result is a mess!
Synchronization primitives are the tools Go gives us to make sure:
- Only one worker touches the data at a time
- Workers wait for each other when needed
- Everyone finishes before we move on
🔒 sync.Mutex — The Bathroom Lock
What Is It?
A Mutex is like a bathroom lock. Only ONE person can use the bathroom at a time. When someone is inside, they lock the door. Others must wait until they unlock it.
How It Works
import "sync"
var counter int
var mu sync.Mutex
func increment() {
mu.Lock() // Lock the door
counter++ // Use the bathroom
mu.Unlock() // Unlock when done
}
Simple Rule
Lock()= “I’m going in, wait for me!”Unlock()= “I’m done, next person can go!”
Real Life Example
Think of a piggy bank. Only one person can put money in at a time, or coins might fall out!
📖 sync.RWMutex — The Library Rule
What Is It?
RWMutex is like a library rule:
- Many people can READ a book at the same time
- But only ONE person can WRITE (edit) at a time
- When someone is writing, nobody can read
How It Works
var data string
var rwmu sync.RWMutex
func readData() string {
rwmu.RLock() // Reading lock
defer rwmu.RUnlock()
return data
}
func writeData(s string) {
rwmu.Lock() // Writing lock
defer rwmu.Unlock()
data = s
}
When to Use It?
- Use RWMutex when you read MORE than you write
- It’s faster because many can read together!
graph TD A["RWMutex"] --> B["RLock - Many Readers OK"] A --> C["Lock - Only 1 Writer"] B --> D["Read Data"] C --> E["Write Data"]
🛎️ sync.Cond — The Dinner Bell
What Is It?
Cond (Condition Variable) is like a dinner bell. Workers wait until someone rings the bell to tell them “Time to eat!” or “Time to work!”
How It Works
var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready bool
// Worker waits for signal
func worker() {
mu.Lock()
for !ready {
cond.Wait() // Sleep until bell
}
// Do work
mu.Unlock()
}
// Boss rings the bell
func boss() {
mu.Lock()
ready = true
cond.Broadcast() // Wake everyone!
mu.Unlock()
}
Key Methods
| Method | What It Does |
|---|---|
Wait() |
Go to sleep, wait for bell |
Signal() |
Wake up ONE waiter |
Broadcast() |
Wake up ALL waiters |
🗺️ sync.Map — The Thread-Safe Treasure Chest
What Is It?
A regular Go map is NOT safe when multiple goroutines use it. sync.Map is a special treasure chest that multiple workers can safely put things in and take things out of!
How It Works
var m sync.Map
// Store a treasure
m.Store("gold", 100)
// Get a treasure
value, ok := m.Load("gold")
if ok {
fmt.Println("Found:", value)
}
// Delete a treasure
m.Delete("gold")
Key Methods
| Method | What It Does |
|---|---|
Store(key, value) |
Put item in chest |
Load(key) |
Get item from chest |
Delete(key) |
Remove item |
Range(func) |
Look at everything |
When to Use?
- When keys are written once, read many times
- When goroutines work on different keys
⚠️ Race Conditions — The Scary Bug
What Is It?
A race condition is when two workers try to change the same thing at the same time. The result depends on who finishes first—like a race!
The Problem
var count = 0
// Two goroutines run this
func addOne() {
count = count + 1
}
What can go wrong?
- Worker A reads
count= 0 - Worker B reads
count= 0 - Worker A writes
count= 1 - Worker B writes
count= 1
Expected: 2 | Got: 1 | Bug!
The Fix: Use a Mutex!
var mu sync.Mutex
func addOneSafe() {
mu.Lock()
count = count + 1
mu.Unlock()
}
graph TD A["Race Condition"] --> B["Two workers"] B --> C["Same data"] C --> D["No protection"] D --> E["CRASH or Wrong Result"]
🔍 Race Detector — The Bug Hunter
What Is It?
Go has a built-in bug hunter called the Race Detector. It watches your program run and tells you if there’s a race condition!
How to Use It
go run -race myprogram.go
go test -race ./...
go build -race
What It Shows
WARNING: DATA RACE
Write by goroutine 7:
main.addOne()
main.go:10
Previous write by goroutine 6:
main.addOne()
main.go:10
Pro Tip
- Always run tests with
-raceflag - It slows down your program, so use it in development only
- Fix every race it finds!
⏳ sync.WaitGroup — The Roll Call
What Is It?
WaitGroup is like a teacher doing roll call. The teacher waits until ALL students arrive before starting class.
How It Works
var wg sync.WaitGroup
func worker(id int) {
defer wg.Done() // "Present!"
fmt.Println("Worker", id)
}
func main() {
for i := 1; i <= 3; i++ {
wg.Add(1) // Expect 1 student
go worker(i)
}
wg.Wait() // Wait for all
fmt.Println("All done!")
}
Key Methods
| Method | What It Does |
|---|---|
Add(n) |
Expect n more workers |
Done() |
One worker finished |
Wait() |
Block until all done |
The Golden Rule
- Call
Add()BEFORE starting goroutine - Call
Done()INSIDE the goroutine - Call
Wait()when you need everyone
graph TD A["Main"] --> B["Add 3"] B --> C["Start Worker 1"] B --> D["Start Worker 2"] B --> E["Start Worker 3"] C --> F["Done"] D --> G["Done"] E --> H["Done"] F --> I["Wait completes"] G --> I H --> I I --> J["Continue"]
🎯 sync.Once — The “Just Once” Magic
What Is It?
sync.Once makes sure something happens exactly one time, no matter how many goroutines try to do it. It’s like a birthday candle—you only blow it out once!
How It Works
var once sync.Once
var config string
func loadConfig() {
once.Do(func() {
config = "loaded!"
fmt.Println("Config loaded")
})
}
func main() {
for i := 0; i < 10; i++ {
go loadConfig()
}
// "Config loaded" prints ONCE
}
Common Uses
- Initialize something once (database, config)
- Singleton pattern (only one instance)
- Close a resource exactly once
Important!
- The function inside
Do()runs exactly once - Even if many goroutines call it
- Even if the first call panics
🎬 Summary: Your Synchronization Toolkit
| Tool | Analogy | Use When |
|---|---|---|
| Mutex | Bathroom lock | One at a time |
| RWMutex | Library rule | Many readers, few writers |
| Cond | Dinner bell | Wait for signal |
| sync.Map | Safe treasure chest | Concurrent map access |
| WaitGroup | Roll call | Wait for all workers |
| Once | Birthday candle | Run exactly once |
Quick Decision Guide
graph TD A["Need to protect data?"] -->|Yes| B["More reads than writes?"] A -->|No| C["Need to wait?"] B -->|Yes| D["Use RWMutex"] B -->|No| E["Use Mutex"] C -->|For all workers| F["Use WaitGroup"] C -->|For a signal| G["Use Cond"] C -->|Just once| H["Use Once"]
🚀 You Did It!
You now understand Go’s synchronization toolkit! Remember:
- Race conditions are bugs — always protect shared data
- Use
-raceflag — let Go find the bugs for you - Pick the right tool — Mutex for simple, RWMutex for reads, WaitGroup for waiting
Go forth and write safe, concurrent code! 🎉
