
该 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 main
import "fmt"
func int_channel(id string, i chan int, o chan int) {
defer close(o)
for x := range i {
fmt.Printf("goroutine '%s': %d\n", id, x)
o <- x + 1
}
fmt.Printf("goroutine '%s': done\n", 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: %d\n", x)
}
}? 注意事项:
- 所有通道必须被正确关闭(由发送方 close(),接收方 range 安全遍历),否则 range c3 会永远阻塞;
- 若需更高吞吐,可考虑为中间通道添加小缓冲(如 make(chan int, 1)),但本质仍需保证生产与消费并发进行;
- 使用 select + default 可实现非阻塞发送(适用于丢弃策略),但本例目标是可靠传递,故不适用。
总结:Go 中的无缓冲通道要求发送与接收严格配对、并发协作。切勿在单个 goroutine 中顺序完成“全量发送 → 后续消费”,而应让生产者与消费者以 goroutine 形式并行运行,这是避免此类死锁的根本原则。

















