go标准库未提供sync.barrier,推荐用sync.waitgroup配合关闭的chan struct{}实现一次性栅栏,确保所有goroutine就绪后统一推进,避免死锁与泄漏。

Go 里没有内置 Barrier,得自己造
Go 标准库确实没提供 sync.Barrier 这种类型。它不像 Java 的 CyclicBarrier 或 Python 的 threading.Barrier 那样开箱即用。原因很实际:Go 倾向用 channel + sync.WaitGroup 组合表达同步逻辑,而不是抽象出一个新原语。直接套用其他语言的 Barrier 模式,容易写出阻塞死锁或 goroutine 泄漏的代码。
常见错误现象是:多个 goroutine 在等待某个条件时,其中一部分提前退出或 panic,导致其余 goroutine 永远卡在 barrier.Wait() 上 —— 因为没人来“凑齐人数”。
- 最稳妥的做法是用
sync.WaitGroup+ 关闭的chan struct{}实现一次性栅栏(one-shot barrier) - 如果需要重复使用(cyclic),必须重置计数器和信号通道,且要确保所有参与者都看到“上一轮已结束”的状态
- 避免用
time.Sleep或轮询代替真正的同步,这会掩盖竞态,且在高负载下失效
用 sync.WaitGroup + chan struct{} 实现可靠的一次性栅栏
这是生产环境最常被验证过的模式:WaitGroup 负责计数,channel 负责广播“所有人到齐了”。关键在于 channel 只关闭一次,所有等待者都能收到零值信号,且不会重复关闭。
使用场景:启动阶段初始化(如加载配置、连接 DB、预热缓存),要求所有子模块就绪后才继续主流程。
立即学习“go语言免费学习笔记(深入)”;
var wg sync.WaitGroup
ready := make(chan struct{})
// 启动 N 个 goroutine
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// ... do work ...
<-ready // 等待栅栏放开
}()
}
// 所有 goroutine 启动完毕后,关闭 ready 通道
wg.Wait()
close(ready)
-
wg.Wait()必须在close(ready)前调用,否则部分 goroutine 可能还没执行到就结束了 - 不能用
buffered chan模拟,因为接收端无法区分“信号已发”和“信号已被消费”,易丢通知 - 这个模式不适用于动态增减参与者的场景;若需弹性,得换用
sync.Map+ 状态机管理
重复使用的栅栏(Cyclic Barrier)必须手动重置状态
标准库不提供循环能力,所以每次“放行”后,你得显式重置计数器和信号源。难点不在实现,而在避免竞争:两个 goroutine 可能同时判断“该重置了”,导致 double-close 或漏通知。
参数差异明显:sync.WaitGroup 本身不可重用,但可以封装一层结构体,内嵌 sync.Mutex 控制重置逻辑;channel 则必须每次新建(不能复用已关闭的)。
- 每次
Wait()前,先mu.Lock(),检查当前轮次是否已完成;未完成则waitCh = make(chan struct{}) - 最后一个到达者负责
close(waitCh)并更新轮次号,其他 goroutine 仅等待 - 别试图用
atomic.Int64替代 mutex —— 关闭 channel 是非原子操作,必须加锁保护 - 性能影响不大,但要注意:频繁创建/销毁 channel 会产生小对象分配,QPS 极高时建议池化
chan struct{}
别把 sync.Once 当 Barrier 用
sync.Once 解决的是“只执行一次”,不是“等所有人到齐再一起走”。误用会导致部分 goroutine 认为自己是第一个而抢先执行,其余则永远等不到信号。
典型错误现象:once.Do(func(){ close(ready) }) 放在每个 goroutine 里 —— 结果只有第一个 goroutine 触发关闭,其他卡死在 。
-
sync.Once适合初始化单例、加载全局配置,不适合协调多个并发实体的同步点 - 如果真想省事,宁可用
sync.WaitGroup+sync.Once组合:Once 控制“谁来发信号”,WaitGroup 控制“谁该等” - 更隐蔽的坑:在测试中用
runtime.Gosched()试图“让出时间片”模拟并发,但实际调度不可控,这种写法既难测又不可靠
真正麻烦的从来不是怎么写通,而是怎么保证所有参与者对“栅栏状态”的认知完全一致——尤其在有超时、取消、panic 恢复的场景下,channel 关闭时机和 WaitGroup 计数匹配稍有偏差,整条链路就挂住不动了。










