You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Go程序报‘fatal error: all goroutines are asleep - deadlock!’死锁错误?

关于Go语言生产者消费者通道的死锁问题

我是Go语言新手,在尝试典型的生产者消费者通道实验时,为做测试让生产者和消费者分别使用不同通道,代码如下:

package main

import (
    "fmt"
    "sync"
)

func main() {

    var wg sync.WaitGroup
    wg.Add(2)

    fmt.Println("Starting the main application")

    channel := make(chan int)
    channel1 := make(chan int)

    go generateNumbersV2(&wg, channel)
    go printNumbersV2(&wg, channel1)

    fmt.Println("Waiting for other goroutines")
    wg.Wait()
    //close()
    //close(channel)

    fmt.Println("All goroutines finished")
}

func printNumbersV2(wg *sync.WaitGroup, rc <-chan int) {
    defer wg.Done()

    for idx := 0; idx < 3; idx++ {
        val := <-rc
        fmt.Println("******value received from channel ", val)
    }
}

func generateNumbersV2(wg *sync.WaitGroup, wc chan<- int) {
    defer wg.Done()

    for idx := 0; idx < 3; idx++ {
        wc <- idx
        fmt.Println("###value written to channel ", idx)
    }
}

运行程序时出现错误:

fatal error: all goroutines are asleep - deadlock!

我理解当前两个goroutine分别因读取channel1和写入channel而阻塞,程序无法终止,但疑问是:若我确实在等待通道中的值,程序为何直接判定死锁而非无限等待?比如后续可能有其他goroutine向这些通道写入值的情况。


问题分析与解答

1. 代码阻塞的根源

你的代码核心问题是生产者和消费者用了完全无关的两个通道:

  • 生产者generateNumbersV2往channel发数据,但整个程序里没有任何goroutine从这个通道读,无缓冲通道的发送操作会一直阻塞,直到有接收者出现。
  • 消费者printNumbersV2从channel1读数据,但整个程序里没有任何goroutine往这个通道写,无缓冲通道的接收操作也会一直阻塞,直到有数据发送过来。

再加上主goroutine在wg.Wait()处阻塞,等待另外两个goroutine调用wg.Done(),三个goroutine全部陷入阻塞状态。

2. Go的死锁检测逻辑

Go运行时会主动监控所有goroutine的状态,当满足以下两个条件时,就会触发死锁错误终止程序:

  • 所有goroutine都处于阻塞状态
  • 不存在任何能解除阻塞的可能性(没有新goroutine会被创建,没有通道会有新的读写操作)

在你的场景里,运行时能明确判断:

  • 主goroutine只会一直等wg.Wait(),不会做任何其他操作
  • 生产者goroutine的发送操作永远找不到接收者,无法推进
  • 消费者goroutine的接收操作永远找不到发送者,无法推进

整个程序已经没有任何继续执行的可能,所以不会无限等待,而是直接抛出死锁错误。

如果是存在其他唤醒可能的场景——比如主goroutine在某个循环里会往通道写数据,或者会启动新的goroutine——那运行时就不会判定死锁,因为程序还有推进的可能。但你的代码里没有任何这样的路径。

3. 修复建议

最直接的修复是让生产者和消费者共用同一个通道,让发送和接收对应起来:
把go printNumbersV2(&wg, channel1)改成go printNumbersV2(&wg, channel)。

另外,为了避免消费者在生产者写完后继续阻塞,建议生产者完成发送后关闭通道,消费者用for range循环接收数据(这样能自动感知通道关闭),修改后的代码示例:

func printNumbersV2(wg *sync.WaitGroup, rc <-chan int) {
    defer wg.Done()
    // 用for range接收,通道关闭后自动退出循环
    for val := range rc {
        fmt.Println("******value received from channel ", val)
    }
}

func generateNumbersV2(wg *sync.WaitGroup, wc chan<- int) {
    defer wg.Done()
    defer close(wc) // 发送完成后关闭通道
    for idx := 0; idx < 3; idx++ {
        wc <- idx
        fmt.Println("###value written to channel ", idx)
    }
}

内容的提问来源于stack exchange,提问作者TruckDriver

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 21:23:27