最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Why Does This Go Program Hang
时间:2026-07-13 09:16:47 编辑:袖梨 来源:一聚教程网
该 Go 程序因通道阻塞导致死锁:主 goroutine 在向 c0 发送数据时被阻塞,而消费 c3 的循环尚未启动,致使中间通道逐步填满并最终无法继续写入。
该 go 程序因通道阻塞导致死锁:主 goroutine 在向 `c0` 发送数据时被阻塞,而消费 `c3` 的循环尚未启动,致使中间通道逐步填满并最终无法继续写入。
在 Go 中,无缓冲通道(unbuffered channel)的发送操作会阻塞,直到有协程准备接收;同理,接收操作也会阻塞,直到有协程准备发送。本例中所有通道均为无缓冲通道(make(chan int)),因此形成一条严格同步的流水线:
- c0 <- 1 → 阻塞等待 int_channel("one") 读取
- "one" 读取后计算 1+1=2 → 尝试 c1 <- 2 → 阻塞等待 "two" 读取
- "two" 读取后发 c2 <- 3 → 阻塞等待 "three" 读取
- "three" 读取后发 c3 <- 4 → 但此时主 goroutine 还未进入 for x := range c3 循环 → c3 发送永久阻塞
关键问题在于:主 goroutine 先执行全部 c0 <- ... 写入,再启动 range c3 消费。由于 c3 无缓冲,第一个值 4 就会让 "three" 协程卡在 c3 <- 4,后续 "two" 卡在 c2 <- ...,"one" 卡在 c1 <- ...,最终 c0 <- 10 也无法完成 —— 所有 goroutine 均陷入等待,触发 fatal deadlock。
✅ 正确解法是并发地生产与消费:将输入发送逻辑放入独立 goroutine,确保 range c3 能及时消费结果,释放上游通道。
以下是修复后的完整代码:
package mainimport "fmt"func int_channel(id string, i chan int, o chan int) { defer close(o) for x := range i { fmt.Printf("goroutine '%s': %dn", id, x) o <- x + 1 } fmt.Printf("goroutine '%s': donen", id)}func main() { c0 := make(chan int) c1 := make(chan int) c2 := make(chan int) c3 := make(chan int) go int_channel("one", c0, c1) go int_channel("two", c1, c2) go int_channel("three", c2, c3) // 启动 goroutine 异步发送,避免阻塞主流程 go func() { inputs := []int{1, 10, 100, 1000, 10000, 100000} for _, v := range inputs { c0 <- v } close(c0) fmt.Println("Sent all numbers to c0") }() // 主 goroutine 立即开始消费最终输出 for x := range c3 { fmt.Printf("out: %dn", x) }}
? 注意事项:
- 所有通道必须被正确关闭(由发送方 close(),接收方 range 安全遍历),否则 range c3 会永远阻塞;
- 若需更高吞吐,可考虑为中间通道添加小缓冲(如 make(chan int, 1)),但本质仍需保证生产与消费并发进行;
- 使用 select + default 可实现非阻塞发送(适用于丢弃策略),但本例目标是可靠传递,故不适用。
总结:Go 中的无缓冲通道要求发送与接收严格配对、并发协作。切勿在单个 goroutine 中顺序完成“全量发送 → 后续消费”,而应让生产者与消费者以 goroutine 形式并行运行,这是避免此类死锁的根本原则。
相关文章
- 迷你世界炎狱魔龙如何获得 炎狱魔龙技能图鉴 07-20
- 迷你世界厨房修建攻略 迷你世界厨房搭建方法 07-20
- 洛克王国世界s3赛季什么时候开始 07-20
- 绝区零希格莉德立绘合集 希格莉德值得抽吗 07-20
- 《明日方舟:终末地》塔晶系统介绍 07-20
- 金铲铲之战s18索拉卡羁绊技能分享 07-20