
本文解释为何多个 goroutine 可同时向同一无缓冲 channel 发送数据而不阻塞——关键在于有其他 goroutine 持续接收,使发送操作能及时完成,符合 go 通道的同步语义。
在 Go 中,无缓冲通道(unbuffered channel)本质上是一个同步通信原语:每次 ch 成对发生、严格配对,二者才会同时完成;否则发送方将被阻塞,直到有接收方就绪。
你提供的代码之所以能持续运行,并非因为“允许多次写入未读取”,而是因为 read goroutine 在循环中不断尝试从通道接收数据(配合 time.Sleep 和 select 超时机制),为两个写 goroutine 提供了持续的消费能力:
func read(ch chan string) {
for {
time.Sleep(time.Second) // 模拟处理延迟,但不阻塞通道本身
select {
case res := <-ch:
fmt.Println("received:", res) // ✅ 接收成功 → 解除一个写操作的阻塞
case <-time.After(200 * time.Millisecond):
fmt.Println("timeout: no data received")
}
}
}注意:time.Sleep(time.Second) 并非阻塞通道,它只是让 read goroutine 每秒最多尝试一次接收;而 select 中的
⚠️ 关键注意事项:
- 无缓冲通道没有队列:它不保存任何值。ch
- 阻塞是 goroutine 级别的:write 阻塞不会影响 write2 的执行(反之亦然),Go 调度器会继续调度其他就绪的 goroutine(如 write2 或 read)。
- 无超时的纯发送会死锁:若移除 read goroutine 或其接收逻辑,仅保留 write 和 write2,程序将在第一次 ch
- 竞争与顺序不可控:谁先发送、谁被先接收,取决于调度器和运行时时机,结果是非确定性的(可能交替打印 "write1"/"write2",也可能连续多次同名)。
✅ 正确理解模型:这不是“多写一读”的缓冲队列,而是多个生产者与一个消费者通过同步握手协作——每一次成功发送,背后都隐含一次即时匹配的接收。这正是 Go “不要通过共享内存来通信,而应通过通信来共享内存”理念的典型体现。
总结:多个 goroutine 向同一无缓冲通道写入可行,根本前提是有至少一个 goroutine 持续、及时地接收;通道本身不存储数据,只协调同步。设计时务必确保“发送-接收”在生命周期内严格配对,避免遗漏接收导致死锁。










