Imagine you launch 3 goroutines to clean your room 🧹, wash dishes 🍽, and vacuum 🧼…
You want to wait until all 3 finish before locking the door.
That's what a sync.WaitGroup does.
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Worker", id, "started")
// do some work here
fmt.Println("Worker", id, "done")
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait() // Wait for all workers to finish
fmt.Println("All done!")
}wg.Add(1)says: “I’m adding one goroutine.”wg.Done()says: “I'm done!”wg.Wait()says: “I'll wait until everyone’s done.”
That’s it! Super clean. Super powerful.
Say two goroutines try to change the same variable at the same time — boom! 💥
This is called a race condition.
To fix it, Go gives us a mutex 🔐
A mutex lets only one goroutine use something at a time — like giving one person the key to a room.
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
counter := 0
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
mu.Lock() // Lock it!
counter++
mu.Unlock() // Unlock it!
wg.Done()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}Without the mutex, counter++ could mess up and give wrong results.
Problem | Use
Wait for goroutines | WaitGroup
Shared data being modified | Mutex
Goroutines need to talk | ChannelsAlways unlock what you lock (or use
defer mu.Unlock())Avoid overusing mutexes — channels are often safer
Never call
wg.Done()without first callingwg.Add(1)
WaitGroupis like a “wait until everything’s done” managerMutexis a “you go first, I’ll wait” toolTogether, they make your concurrent code safe and stable
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.