← 中文资源

简体中文版本

Go: 在 nil channel 上收发会永久阻塞

诊断 goroutine 因在 nil channel 上收发而永久挂起,并解释为何死锁检测器有时保持沉默。

阅读英文原文

症状

某个 goroutine 卡住而无任何进展,进程可能打印 fatal error: all goroutines are asleep - deadlock!,也可能在还有其它 goroutine 存活时静默挂起。CPU 占用近乎为零,因为没有任何可运行的任务。

诊断

先确认 channel 在使用前已构造,然后导出 goroutine 栈:

kill -QUIT $(pgrep myapp)

栈信息会显示某个 goroutine 阻塞在特定行的 channel 收发上;据此回溯该 channel 变量的声明。

修复

务必在使用前 make,并为等待设置上限:

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
    }
}

验证

以 -race 并带 context 超时运行。程序能正常结束,SIGQUIT 栈也再不会显示 goroutine 停在原先为 nil 的 channel 上。