本文深入剖析 go 语言中多生产者/多消费者并发模型的典型陷阱,重点揭示全局变量 `seq` 的数据竞争问题、无缓冲 channel 的同步机制本质,并提供基于 `atomic` 和结构化 channel 设计的安全实现方案。
在 Go 并发编程中,“多个生产者 + 多个消费者”看似简单的模式,实则极易因隐式同步假象掩盖底层竞态风险。你提供的代码看似稳定输出 1–1000 的有序序列,但这并非线程安全的保证,而是侥幸的巧合——根源在于未受保护的全局变量 seq 引发的数据竞争(data race),以及对无缓冲 channel 行为的误解。
? 为什么看似“有序”?——无缓冲 channel 的隐式串行化
requestChan 和 generatorChan 均为无缓冲 channel(make(chan uint64)),这意味着:
- 每次
- 同样,generatorChan
这种阻塞特性强制了请求与响应的配对时序,但它完全不保证 seq++ 操作的原子性。多个 generateStuff goroutine 可能同时从 requestChan 接收请求后,几乎同时读取、递增、写回 seq —— 这正是经典的“读-改-写”竞态场景。
⚠️ 危险信号:go run -race main.go 会立即报告 Found 1 data race(s),并定位到 seq = seq + 1 行。竞态后果不可预测:重复值、跳变、甚至 panic。
✅ 正确解法:用原子操作替代共享变量递增
消除竞态最直接的方式是避免对 seq 的非原子访问。使用 sync/atomic 包: