用channel等待goroutine完成最直接:goroutine结束时向chan struct{}或chan error发送信号,主协程通过接收该信号实现同步,无需额外封装或第三方库。

用 channel 等待 goroutine 完成是最直接的方式
Go 没有“阻塞等待异步任务”的内置函数,但 channel 天然就是同步原语。你不需要额外封装、不依赖第三方库,只要让 goroutine 在结束时往一个 chan struct{} 或 chan error 发送信号,主协程 就能卡住直到它完成。
常见错误是用 time.Sleep 代替等待,或误以为启动 goroutine 后“自然会等”,结果主函数提前退出,goroutine 被强制终止。
- 推荐用
chan struct{}表示“完成通知”,零内存开销 - 如果需要返回结果或错误,改用
chan Result(Result是自定义结构体) - 务必确保 goroutine 一定发信号——哪怕 panic 了也要用
defer+recover补发,否则主协程永远卡死
sync.WaitGroup 适合批量 goroutine 的统一收尾
当你启动多个 goroutine 做并行工作(比如并发请求几路 API),且只关心“全部做完”,不用传值,sync.WaitGroup 比 channel 更轻量、语义更清晰。
容易踩的坑是 WaitGroup.Add() 调用时机不对:必须在 goroutine 启动前调用,不能在 goroutine 内部调;否则可能 Add 还没执行,Wait 就已返回。
立即学习“go语言免费学习笔记(深入)”;
-
wg.Add(1)必须在go func() { ... }()之前 - goroutine 内部结尾处必须调
wg.Done(),别漏写 - 不要对同一个
WaitGroup多次调Wait(),它不是可重用的锁 - 如果 goroutine 可能 panic,
Done()要包在defer里
带超时的等待必须用 select + time.After
真实场景中,“无限等”等于线上事故。任何 channel 等待都该配超时,而 Go 的标准做法就是 select 分支加 time.After()。
别用 time.Sleep 后再读 channel——这会强行延迟,且无法响应提前完成;也别自己起 goroutine 写超时逻辑,纯属重复造轮子。
- 超时分支必须用
case ,不是 <code>time.NewTimer().C(除非你要复用 timer) - 如果 channel 是带缓冲的,
仍可能立即返回零值,记得检查业务逻辑是否允许 - 超时后,原 goroutine 可能还在跑,要评估是否需主动取消(这时得引入
context.Context)
需要传参/返回值时,避免用全局变量或闭包捕获
新手常把参数塞进匿名函数闭包,或用包级变量暂存结果,看似能跑,但一并发就出问题:变量被多个 goroutine 交叉写入,结果错乱,且难 debug。
正确方式是把输入和输出都绑定到 channel 通信上,保持 goroutine 间零共享。
- 输入参数通过函数参数传入 goroutine,别靠外部变量
- 返回值走
chan Result,每个 goroutine 自己建自己的 channel 实例 - 如果参数大(比如 []byte),传递指针或使用
sync.Pool避免频繁分配,但别因此退回到共享变量
最易被忽略的是 goroutine 的生命周期管理:没人告诉 channel 接收方“发送者已退出”,所以带缓冲的 channel 不填满就永远不会触发接收;而无缓冲 channel 若发送端 panic 未发信号,接收端就彻底卡死。这类问题在线上不会报错,只会静默 hang 住。










