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

为何注释Go代码中fmt.Printf语句会引发死锁?

Go代码注释特定语句引发死锁的原因分析

核心前提:无缓冲通道的特性

Go中用make(chan int)创建的是无缓冲通道,这类通道的发送(ch <- val)和接收(<-ch)操作必须同步配对完成——发送方会阻塞直到有接收方准备好接收,反之接收方也会阻塞直到有发送方准备好发送。

保留fmt.Printf("1a %d\n", value)时的执行逻辑

当保留这条打印语句时,执行流程大致如下:

  1. 主goroutine启动两个子goroutine后,执行ch1 <- 42,此时goroutine1正卡在<-ch1等待接收,两者配对完成,goroutine1拿到值42。
  2. goroutine1执行fmt.Printf("1a %d\n", value),这是IO操作,会触发Go调度器切换到其他goroutine(比如主goroutine或goroutine2)。
  3. 主goroutine继续执行ch2 <- 0,此时goroutine2已经执行完fmt.Printf("2a\n"),正卡在<-ch2等待接收,两者配对完成,goroutine2拿到值0。
  4. 主goroutine接下来进入time.Sleep(2 * time.Second),虽然此时goroutine1卡在ch2 <- 42(goroutine2已经接收过ch2的0,不会再处理ch2的发送)、goroutine2卡在ch1 <- 0(没有其他goroutine接收ch1),但主goroutine的sleep是定时阻塞,到时间后会被唤醒,执行打印并退出程序。由于主goroutine最终会主动结束,程序不会触发死锁检测(死锁检测只在所有goroutine永久阻塞时触发)。

注释fmt.Printf("1a %d\n", value)时的死锁逻辑

去掉这条打印后,goroutine1的执行速度大幅提升,调度顺序发生变化,最终导致所有活跃goroutine永久阻塞:

  1. 主goroutine执行ch1 <- 42,和goroutine1的<-ch1配对完成后,goroutine1跳过IO操作,直接执行ch2 <- 42。
  2. 此时如果goroutine2的<-ch2先和goroutine1的ch2 <- 42配对:
    • goroutine2拿到值42,执行ch1 <- 42,但此时没有任何goroutine在接收ch1(goroutine1已经接收过一次,主goroutine也没有接收逻辑),goroutine2永久阻塞。
    • 主goroutine继续执行ch2 <- 0,此时没有goroutine接收ch2,主goroutine永久阻塞。
    • goroutine1完成ch2 <- 42后执行打印并退出,但剩下的主goroutine和goroutine2都永久阻塞,触发Go的死锁检测,程序panic。
  3. 另一种调度顺序下,主goroutine的ch2 <- 0先和goroutine2的<-ch2配对:
    • goroutine2拿到值0,执行ch1 <- 0,无接收方,永久阻塞。
    • goroutine1卡在ch2 <- 42,无接收方,永久阻塞。
    • 此时所有活跃goroutine都处于永久阻塞状态,Go的死锁检测会立即触发panic,主goroutine的sleep流程根本走不完。

总结

这条fmt.Printf语句的核心作用是触发goroutine调度切换,改变了程序的执行顺序,使得主goroutine能在子goroutine永久阻塞前完成sleep并主动退出,从而避开死锁检测;而去掉这条语句后,子goroutine的执行节奏打乱,最终导致所有goroutine进入永久阻塞的循环,触发死锁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 22:09:56