结论:goroutine间同步首选sync.waitgroup;需协调顺序或传值时用chan;共享变量读写必须配sync.mutex或原子操作。waitgroup.add(n)须在goroutine启动前调用,done()推荐defer调用,wait()阻塞至计数归零,仅表达完成信号,不传数据。

直接说结论:goroutine 间数据同步,首选 sync.WaitGroup;需要协调执行顺序或传递值时,才用 chan;共享变量读写必须配 sync.Mutex 或原子操作。
别被“同步”二字带偏——Go 里没有“锁住整个流程”的惯性思维,而是按场景选最轻、最明确的机制。
用 sync.WaitGroup 等待 goroutine 完成最稳妥
这是绝大多数“主函数等所有子任务结束”场景的标准解法,不传参、不耦合、不依赖缓冲区大小。
-
WaitGroup.Add(n)必须在 goroutine 启动前调用,且不能在 goroutine 内部调用(否则可能漏加或竞态) - 每个 goroutine 结尾必须调
wg.Done(),推荐用defer wg.Done()防止 panic 时漏掉 -
wg.Wait()会阻塞直到计数归零,之后可安全读取共享变量或关闭通道 - 它不传递数据,只表达“完成信号”,所以不适合做状态通知或轮转控制
用 chan 做同步时,必须分清“信号”和“数据”
通道不是万能同步开关,用错就死锁。关键看你要同步的是“时机”还是“内容”。
- 无缓冲通道(
make(chan struct{}))适合一对一唤醒,比如 A 打印完发信号给 B,B 收到才开始打印 - 带缓冲通道(如
make(chan bool, 2))可批量收信号,但容量必须严格等于 goroutine 数量,否则可能阻塞或丢失 - 从已关闭通道接收会立刻返回零值+
false,不能靠for range处理多个不同步关闭的通道(容易漏判或死循环) - 别用
chan int来同步计数器——那是共享状态,该上Mutex或atomic.Int64
共享变量读写必须加锁或用原子操作
哪怕只是 i++,在多个 goroutine 并发执行时也是非原子的——底层是读-改-写三步,中间可能被抢占。
- 简单整数计数优先用
atomic.AddInt64(&i, 1),比Mutex更轻量、无阻塞 - 结构体字段多、逻辑复杂时,用
sync.Mutex包裹读写块,注意锁粒度:别把fmt.Println这类耗时操作包进临界区 - 不要用 channel 替代锁来保护变量——比如用一个
chan int串行修改值,性能差、语义不清、扩展性为零 - 切片、map 不是线程安全的,即使只读也要注意:map 在扩容时可能被其他 goroutine 写入,引发 panic
真正容易被忽略的点是:同步机制的选择本质是契约设计。你用 WaitGroup,就承诺“只关心完成与否”;你用 chan string,就承担了序列化、缓冲、关闭管理的全部责任;你用 Mutex,就得确保每次访问都走同一把锁。混用或误用,问题不会立刻报错,而是在高并发、GC 触发、调度切换时悄然暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











