← Debugging Guides

阅读中文版本

Go: send or receive on a nil channel blocks forever

Diagnose a goroutine that hangs because it sends to or receives from a nil channel, and why the deadlock detector sometimes stays silent.

intermediatedebugginggoconcurrencychannels

Symptoms

  • fatal error: all goroutines are asleep - deadlock!
  • A worker never starts because it waits on a channel that was never made
  • The program hangs with no output and no CPU usage

Possible causes

  • A channel field is left nil and used before make() runs
  • A nil channel is passed into a select, disabling that case silently
  • An unbuffered channel send has no matching receiver
  • The main function returns while a receiver still waits, hiding the hang

Diagnosis steps

Look for make(chan ...) vs zero-value channel use

Interpretation

A nil channel blocks all sends and receives permanently; confirm construction precedes first use.

Dump goroutine stacks when hung

kill -QUIT <pid>  # or send SIGQUIT

Interpretation

The dump shows a goroutine parked on a channel op, naming the channel variable and the line.

Check select cases for nil channels

Interpretation

A nil case in select is never chosen, which can silently drop the intended branch rather than panic.

Root cause

A nil channel has no receiver buffer and never becomes ready: any send or receive on it blocks forever. Go only reports "all goroutines are asleep" when every goroutine is blocked, so a hang with another live goroutine (or a returned main) can occur without the deadlock message.

Fix

Initialise every channel before use with make(chan T) or make(chan T, n), and guard against nil by construction or a nil check. Never send on nil; if a select case must be disabled, pass nil deliberately but document it. Use timeouts or context to bound waits so a missed signal cannot hang forever.

Verify the fix

Run the program with the race detector and a timeout; the send/receive must complete, and killing with SIGQUIT no longer shows a goroutine parked on the nil channel.

Symptoms

A goroutine hangs with no progress and the process may print fatal error: all goroutines are asleep - deadlock! — or may hang silently when another goroutine is still alive. CPU usage is near zero because nothing is runnable.

Diagnosis

Confirm the channel is constructed before use, then dump stacks:

kill -QUIT $(pgrep myapp)

The dump shows a goroutine blocked on a chan send or receive at a specific line; trace that channel’s variable to its declaration.

Fix

Always make channels before use and bound waits:

type Worker struct {
    jobs chan int
}
func NewWorker() *Worker {
    return &Worker{jobs: make(chan int, 8)}
}

func (w *Worker) run(ctx context.Context) {
    select {
    case job := <-w.jobs:
        _ = job
    case <-ctx.Done():
        return
    }
}

Verification

Run with -race and a context timeout. The program finishes, and a SIGQUIT dump no longer shows any goroutine parked on the previously nil channel.