Deadlocks and pitfalls
When every goroutine has stopped to wait, and nobody can give them what they're waiting for, the program can never move again. That's a deadlock. When Go sees that every goroutine is waiting, it stops the program and shows:
fatal error: all goroutines are asleep - deadlock!
This lesson runs the most common deadlocks one by one. Press Step on any of them, or press "See how it went wrong": each goroutine gets its own lane, and you can see where each one stopped and which channel or lock it's waiting on.
A send with no receiver
1package main2 3import "fmt"4 5func main() {6 orders := make(chan string)7 orders <- "order 1"8 fmt.Println(<-orders)9}
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main(...)
/tmp/sandbox/prog.go:7 +0x1d
exit status 2What happened: Every goroutine is waiting (on a channel, a lock or a WaitGroup) and none can go on: a deadlock.
On an unbuffered channel, a send waits until someone receives (lesson 19). The receive is on the next line, but main is stuck on the send on line 7 and never gets there. main is the only goroutine, and it's waiting, so nothing can move.
Under the error, goroutine 1 [chan send]: says goroutine 1 (that's main) stopped while sending on a channel. There are two fixes: let another goroutine do the sending (or the receiving), or give the channel a buffer, make(chan string, 1) (lesson 20).
Forgetting close
for v := range ch keeps receiving until the channel is closed. If the sender finishes without close, the range waits for the next value forever:
1package main2 3import "fmt"4 5func main() {6 prices := make(chan int)7 go func() {8 for _, p := range []int{30, 45, 25} {9 prices <- p10 }11 }()12 total := 013 for p := range prices {14 total += p15 fmt.Println("got", p)16 }17 fmt.Println("total", total)18}
got 30
got 45
got 25
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main(...)
/tmp/sandbox/prog.go:13 +0x1d
exit status 2What happened: Every goroutine is waiting (on a channel, a lock or a WaitGroup) and none can go on: a deadlock.
All three prices arrive, then a deadlock at the range on line 13. Press Step to the end: the lane sending prices has finished, and only main is left, waiting on prices for a value that will never come.
The sender closes the channel when it's done. The safest way is defer close(prices) at the top of the goroutine:
1package main2 3import "fmt"4 5func main() {6 prices := make(chan int)7 go func() {8 defer close(prices)9 for _, p := range []int{30, 45, 25} {10 prices <- p11 }12 }()13 total := 014 for p := range prices {15 total += p16 }17 fmt.Println("total", total)18}
total 100
Remember: whoever sends, closes. The receiver shouldn't close a channel, because it can't know whether the sender has more to send.
Forgetting Done
wg.Wait() waits for the counter to get back to 0. If one goroutine never calls Done, the counter never gets there:
1package main2 3import (4 "fmt"5 "sync"6)7 8func main() {9 sizes := []int{1200, 0, 800}10 thumbs := make([]int, len(sizes))11 var wg sync.WaitGroup12 for i, s := range sizes {13 wg.Add(1)14 go func() {15 if s == 0 {16 return17 }18 thumbs[i] = s / 1019 wg.Done()20 }()21 }22 wg.Wait()23 fmt.Println(thumbs)24}
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [sync.WaitGroup.Wait]:
main.main(...)
/tmp/sandbox/prog.go:22 +0x1d
exit status 2What happened: Every goroutine is waiting (on a channel, a lock or a WaitGroup) and none can go on: a deadlock.
The image of size 0 returns early and skips wg.Done(). The counter stays at 1, and main stops at wg.Wait() on line 22. That's why the last lesson said: put defer wg.Done() on the goroutine's first line, so it runs however the goroutine ends.
Another way to miss a Done: passing a WaitGroup to a function as a copy, not a pointer.
1package main2 3import (4 "fmt"5 "sync"6)7 8func check(order string, wg sync.WaitGroup) {9 defer wg.Done()10 fmt.Println("checking", order)11}12 13func main() {14 var wg sync.WaitGroup15 wg.Add(1)16 go check("order 7", wg)17 wg.Wait()18 fmt.Println("all checked")19}
checking order 7
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [sync.WaitGroup.Wait]:
main.main(...)
/tmp/sandbox/prog.go:17 +0x1d
exit status 2What happened: Every goroutine is waiting (on a channel, a lock or a WaitGroup) and none can go on: a deadlock.
check finished and did call Done, but on the copy's counter. The wg in main is still at 1. The parameter should be wg *sync.WaitGroup, and the call should pass &wg. go vet warns about this (passes lock by value: a WaitGroup must not be copied).
Locking the same lock twice
1package main2 3import (4 "fmt"5 "sync"6)7 8type Cart struct {9 mu sync.Mutex10 items []int11}12 13func (c *Cart) Total() int {14 c.mu.Lock()15 defer c.mu.Unlock()16 sum := 017 for _, p := range c.items {18 sum += p19 }20 return sum21}22 23func (c *Cart) Add(p int) {24 c.mu.Lock()25 defer c.mu.Unlock()26 c.items = append(c.items, p)27 fmt.Println("added", p, "total", c.Total())28}29 30func main() {31 c := &Cart{}32 c.Add(20)33}
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [sync.Mutex.Lock]:
main.(*Cart).Total(...)
/tmp/sandbox/prog.go:14 +0x1d
main.(*Cart).Add(...)
/tmp/sandbox/prog.go:27 +0x1d
main.main(...)
/tmp/sandbox/prog.go:32 +0x1d
exit status 2What happened: Every goroutine is waiting (on a channel, a lock or a WaitGroup) and none can go on: a deadlock.
Add holds the lock and calls Total, which tries to Lock the same lock. The lock belongs to Add, so Total waits for Add to unlock, and Add is waiting for Total to return. It waits for itself, on line 14. Go's Mutex doesn't remember who locked it: the same goroutine locking again has to wait like anyone else.
The fix: put the calculation in a lowercase helper method that doesn't lock (say, total()), and have Total and Add each call it once they hold the lock.
What happens when this program runs?
1package main2 3import "fmt"4 5func main() {6 ch := make(chan int, 2)7 ch <- 18 ch <- 29 ch <- 310 fmt.Println(<-ch, <-ch, <-ch)11}
The buffer has room for 2. The first two values go into the buffer; the third send waits for someone to take one out, but the receives come later, and main never gets there. One goroutine, waiting: a deadlock.
One Done or Unlock too many
The other way around: more Dones than Adds makes the counter negative, and the program panics at once:
1package main2 3import (4 "fmt"5 "sync"6)7 8func main() {9 orders := []string{"order 1", "order 2", "order 3"}10 var wg sync.WaitGroup11 wg.Add(2)12 for _, o := range orders {13 fmt.Println("handling", o)14 wg.Done()15 }16 wg.Wait()17}
handling order 1
handling order 2
handling order 3
panic: sync: negative WaitGroup counter
goroutine 1 [running]:
main.main(...)
/tmp/sandbox/prog.go:14 +0x1d
exit status 2What happened: Done was called more times than Add.
Three orders, but only Add(2). The third Done takes the counter to -1: sync: negative WaitGroup counter. Use wg.Add(len(orders)), or wg.Add(1) right before each go, and the count can't be off.
Locks are the same: Unlock a Mutex that isn't locked and the program stops with fatal error: sync: unlock of unlocked mutex. A common cause is writing defer mu.Unlock() and then also calling Unlock by hand.
Stuck without an error
Go only reports a deadlock when every goroutine is waiting. If main can still finish, a stuck goroutine doesn't cause an error. It just waits there, quietly, forever:
1func main() {2 results := make(chan int)3 go func() {4 results <- 42 // nobody receives: this goroutine waits here forever5 }()6 fmt.Println("leaving without the result")7}
This program ends normally, with no error. In a short program that's harmless; in a server that runs for months, stuck goroutines pile up until they fill the memory. This is a goroutine leak. Every time you start a goroutine, ask yourself: how does it end?
A checklist
When you see all goroutines are asleep, first find the line where main stopped, then press Step to see what the other lanes are waiting for:
main stopped at | Common cause | Fix |
|---|---|---|
ch <- v | Nobody receives | Receive in another goroutine, or add a buffer |
for v := range ch | The sender never closed it | close(ch) when done, best as defer close(ch) |
wg.Wait() | A goroutine missed Done, or the WaitGroup was copied | defer wg.Done(), pass &wg |
mu.Lock() | The lock was never given back, or locked twice by one goroutine | defer mu.Unlock(), don't call locking code while holding the lock |
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it. All three programs deadlock right now.
Fix it: prices nobody receives
✓sum tries to send every price into the channel first and only then receive them to add up, but the very first send gets stuck. Make the program show the totals of both price lists. (You don't need to change main.)
100 20
1package main2 3import "fmt"4 5func sum(prices []int) int {6 ch := make(chan int)7 for _, p := range prices {8 ch <- p9 }10 total := 011 for range prices {12 total += <-ch13 }14 return total15}16 17func main() {18 fmt.Println(sum([]int{30, 45, 25}))19 fmt.Println(sum([]int{12, 8}))20}
Fix it: canceled orders that keep Wait waiting
✓Each order gets a goroutine that works out what to pay (quantity × unit price); orders with quantity 0 were canceled and are skipped. But the program gets stuck at wg.Wait(). Make it show the amount for each order.
[30 0 75 0 15]
1package main2 3import (4 "fmt"5 "sync"6)7 8func main() {9 qty := []int{2, 0, 5, 0, 1}10 price := 1511 amounts := make([]int, len(qty))12 var wg sync.WaitGroup13 for i, q := range qty {14 wg.Add(1)15 go func() {16 if q == 0 {17 return18 }19 amounts[i] = q * price20 wg.Done()21 }()22 }23 wg.Wait()24 fmt.Println(amounts)25}
Fix it: a cart that locks inside its lock
✓Add holds the lock and calls Total, which locks the same lock again. Add a method total() that adds up the cart without locking, and have both Total and Add use it. The program puts three things in the cart.
added 20 total 20 added 35 total 55 added 5 total 60 final 60
1package main2 3import (4 "fmt"5 "sync"6)7 8type Cart struct {9 mu sync.Mutex10 items []int11}12 13func (c *Cart) Total() int {14 c.mu.Lock()15 defer c.mu.Unlock()16 sum := 017 for _, p := range c.items {18 sum += p19 }20 return sum21}22 23func (c *Cart) Add(p int) {24 c.mu.Lock()25 defer c.mu.Unlock()26 c.items = append(c.items, p)27 fmt.Println("added", p, "total", c.Total())28}29 30func main() {31 c := &Cart{}32 c.Add(20)33 c.Add(35)34 c.Add(5)35 fmt.Println("final", c.Total())36}
What you learned
- When every goroutine is waiting and none can move, that's a deadlock:
fatal error: all goroutines are asleep - deadlock!. The error shows the line wheremainstopped; Step shows what each lane is waiting for. - A send on an unbuffered channel needs another goroutine to receive.
- To
rangeover a channel, the sender mustcloseit when done: whoever sends, closes. - Put
defer wg.Done()on a goroutine's first line; pass a WaitGroup as a pointer. - Go's
Mutexcan't be locked twice, not even by the same goroutine; put the work you need inside the lock in a helper that doesn't lock. - More
Dones thanAdds is a panic; unlocking an unlocked lock is a fatal error. - When only some goroutines are stuck, there's no error: they leak quietly. Make sure every goroutine has a way to end.