本文深入解析 go `select` 语句中对通道的读写行为,重点说明重复接收(`
Go 的 select 语句是实现非阻塞或多路通道通信的核心机制,其行为类似于 switch,但每个 case 都对应一个通道操作(发送或接收)。关键在于:每个 ——它不是“窥探”或“检查”,而是原子性的“接收并移除”。
在原始正确代码中:
case s := <-quit: fmt.Println("quit =", s) return
而修改后的错误代码:
case <-quit: fmt.Println(<-quit) // ⚠️ 危险!第二次接收
问题出现在第二行:case 再次接收,但 main goroutine 中只向 quit 发送了一次(quit 无人再向 quit 发送新值。因此,
✅ 正确实践原则
- 一个 :避免在同一个逻辑路径中多次对同一通道执行接收操作。
- 接收后立即使用或丢弃:若只需信号语义(如退出通知),可直接用空接收 case
- 确保发送方与接收方配对:本例中 quit 仅发送一次,接收逻辑也必须严格匹配一次。
? 改进示例(增强健壮性)
为更清晰体现信号语义,可进一步优化退出逻辑:
func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: // 纯信号,无需读取具体值 fmt.Println("received quit signal") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 9 // 发送退出信号 }() fibonacci(c, quit) }
? 小结:select 的每个 case 是独立的通道操作,