Go DISCUSSION

fatal error: all goroutines are asleep - deadlock! when sending on a Go channel: why?

Started by shammypeterson Go channelsgoroutine deadlockunbuffered channelsync.WaitGroupclose channel
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

fatal error: all goroutines are asleep - deadlock! when sending on a Go channel: why?

shammypeterson Go Forum
#1

My first channel program is four statements: ch := make(chan int); ch <- 42; v := <-ch; fmt.Println(v). It aborts with "fatal error: all goroutines are asleep - deadlock!" on the send. A second attempt, which starts three worker goroutines that send results and then ranges over the channel in main, prints the three results and then dies with the same error.

Why can I not send and then receive on the same channel in one goroutine, and what is the correct way to collect results from several goroutines and finish cleanly?

Community replies 5

Re: fatal error: all goroutines are asleep - deadlock! when sending on a Go channel: why?

#2

make(chan int) creates an unbuffered channel. A send on it blocks until another goroutine is ready to receive, and a receive blocks until another goroutine sends; the value is handed over directly when the two meet. Your main goroutine blocks at ch <- 42 waiting for a receiver, but the only receiver is the next line of the same goroutine, which is never reached. The runtime sees that every goroutine is blocked with nothing left to wake them, and aborts.

Either do the send in another goroutine, go func() { ch <- 42 }() followed by v := <-ch, or give the channel a buffer: make(chan int, 1) lets one send complete without a waiting receiver.

Re: fatal error: all goroutines are asleep - deadlock! when sending on a Go channel: why?

#3

The second program fails for a different reason. for v := range ch keeps receiving until the channel is closed. After the three results, main blocks waiting for a fourth value, no goroutine is left to send one, and that is again a deadlock. The sending side has to call close(ch) once all sends are done.

With several senders, none of them knows when the others have finished, so use a sync.WaitGroup and close from a separate goroutine: call wg.Add(3) before starting the workers, have each worker defer wg.Done(), then start go func() { wg.Wait(); close(ch) }() and range over ch in main. The loop ends cleanly when the channel is closed and drained.

Re: fatal error: all goroutines are asleep - deadlock! when sending on a Go channel: why?

#4

The rules for closing: only the sender closes, never the receiver; sending on a closed channel panics; closing twice panics. Receiving from a closed channel never blocks. It yields any buffered values that remain and after that zero values, and the two-value form v, ok := <-ch reports ok == false once the channel is closed and empty.

You do not have to close a channel to free it; one that is no longer referenced is garbage-collected. Close only to tell receivers that no more values are coming. When the number of results is known, you can skip closing and simply count: for i := 0; i < 3; i++ { v := <-ch; ... }.

Re: fatal error: all goroutines are asleep - deadlock! when sending on a Go channel: why?

#5

A buffered channel such as make(chan int, 3) blocks a sender only when three values are already waiting, and a receiver only when it is empty. That decouples bursts, but it is not a cure for deadlock: a fourth unreceived send blocks just the same. Size the buffer by design, for example one slot per worker, so that every worker can deliver its result and exit even if the consumer has given up.

Bear in mind that the deadlock detector only fires when all goroutines are blocked. In a real program with a server or a ticker still running, a stuck goroutine just leaks quietly. Guard blocking operations with select and a timeout or a context: select { case v := <-ch: handle(v); case <-time.After(2 * time.Second): return errTimeout }.

Re: fatal error: all goroutines are asleep - deadlock! when sending on a Go channel: why?

#6

A few other traps around the same code. A nil channel, declared with var ch chan int and never made, blocks forever on both send and receive, which gives the same fatal error in a small program. Passing a sync.WaitGroup to a worker by value copies it, so Done acts on the copy and Wait never returns; pass a *sync.WaitGroup or capture it in a closure. Call wg.Add before the go statement, not inside the goroutine, or Wait may run before any Add.

Closures started in a loop also deserve a look: before Go 1.22 all iterations shared one loop variable, so goroutines that captured it could all see the same value. In modules that declare Go 1.22 or later each iteration has its own variable.

TEP COMMUNITY