go跨协程共享变量关键在安全使用:for-range中需传参捕获瞬时值;全局变量须用atomic或mutex保护;channel操作要严守关闭规则并检查ok值。

Go 里跨协程共享变量,不是“能不能用”的问题,而是“怎么用才不崩”的问题。直接读写全局变量或循环变量,90% 的时候会出错——不是立刻 panic,而是数据错乱、计数不准、日志乱序,线上跑几天才暴露。
for-range 启动 goroutine 时 i 和 v 总是最后值
这是最典型也最容易被忽略的陷阱。循环变量 i 和 v 在整个 for 范围内复用同一内存地址,所有 goroutine 捕获的是变量的引用,而非每次迭代的快照。
- 错误写法:
go func() { fmt.Println(i, v) }()→ 所有 goroutine 输出相同值(比如4 4) - 正确做法:把当前值作为参数传入闭包,强制捕获瞬时值:
go func(idx int, val int) { fmt.Println(idx, val) }(i, v) - 注意:如果
v是切片或 map,传参仍可能共享底层数组;需copy或重新构造副本
全局变量被多个 goroutine 并发读写没加锁
counter++ 看似原子,实则是读-改-写三步操作。两个 goroutine 同时执行,大概率丢一次更新——最终结果比预期小,且每次运行结果还不一样。
- 别依赖“我只读不写”就放心:哪怕只是
if counter > 100,和另一个 goroutine 的counter++同时发生,就是数据竞争 - 简单计数优先用
atomic.AddInt64(&counter, 1),比sync.Mutex开销小、语义清晰 - 复杂逻辑(如结构体字段组合更新)必须用
sync.Mutex或sync.RWMutex,且锁粒度要细——不要把 HTTP handler 整个包进一个大锁里
channel 关了还在往里 send,或者没关却一直 recv
向已关闭的 channel 发送会 panic;从已关闭且无数据的 channel 接收会立即返回零值——但若你依赖这个零值做业务判断,就埋了雷。
- 发送方负责关闭 channel,接收方永远不要关;多个发送方?改用
errgroup.Group或手动协调关闭时机 - 避免无限
for range ch:如果 channel 可能长期无数据但又不能关,加select+timeout或ctx.Done() - 检查
ok返回值:val, ok := ,<code>!ok表示 channel 已关闭且无剩余数据
真正难的不是记住这些规则,而是在写业务逻辑时下意识去问:“这个变量/通道/结构体,此刻有没有其他 goroutine 在碰它?”——并发安全不是加锁就能解决的事,是每行代码都要带上下文去读。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











