Go中无原生“协程执行组”,并发编排本质是通过WaitGroup、Context和Channel协作控制生命周期、同步与资源边界;因main退出会强制终止所有goroutine,故不能仅用go启动就完事。

直接说结论:Go里没有“协程执行组”这种原生概念,所谓“并发编排”本质是控制 goroutine 的生命周期、同步时机和资源边界——靠 sync.WaitGroup、context.Context 和带缓冲/无缓冲 chan 组合实现,不是靠封装一个“组”类型。
为什么不能直接用 goroutine 启动就完事?
新手常写这样的代码:
for i := 0; i < 5; i++ {
go func() {
fmt.Println("task", i)
}()
}结果输出全是 task 5,因为闭包捕获的是变量 i 的地址,循环结束时 i == 5,所有协程都读到同一个值。
- 必须显式传参:
go func(id int) { ... }(i) - 或在循环内定义新变量:
id := i; go func() { ... }() - 更关键的是:主协程可能提前退出,子协程被强制终止——Go 不会自动等待
WaitGroup 是并发编排的底线要求
sync.WaitGroup 解决“等谁、等多久”的问题,它不负责通信,只负责计数。
立即学习“go语言免费学习笔记(深入)”;
-
wg.Add(n)必须在go语句前调用,且不能漏、不能多 -
wg.Done()必须在每个协程退出前调用(建议用defer wg.Done()) -
wg.Wait()阻塞直到计数归零,但不保证协程执行顺序 - 注意:
WaitGroup不能被复制,也不能在Wait()后重复使用(需重置)
典型写法:
wg := &sync.WaitGroup{}
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("done %d\n", id)
}(i)
}
wg.Wait() // 主协程在此阻塞加 context 控制超时和取消才是生产级编排
WaitGroup 只管“是否完成”,不管“是否该停”。比如某个任务卡死、网络请求挂起,你得能主动中断它。
-
context.WithTimeout或context.WithCancel提供信号通道 - 每个协程需监听
ctx.Done()并及时退出(尤其在 I/O 或循环中) - 不要忽略
ctx.Err()—— 它告诉你为何被取消:context.DeadlineExceeded还是context.Canceled -
context.WithCancel的 cancel 函数应在合适时机调用(如某子任务失败后全局取消)
示例片段:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
<p>for i := 0; i < 3; i++ {
go func(id int) {
select {
case <-time.After(1 * time.Second):
fmt.Printf("success %d\n", id)
case <-ctx.Done():
fmt.Printf("canceled %d: %v\n", id, ctx.Err())
}
}(i)
}</p><p>// 不用 WaitGroup,靠 ctx 超时自然结束channel 不是万能胶,用错反而增加复杂度
很多人以为“用 channel 就是 Go 并发”,其实 channel 主要解决两类问题:数据传递、同步协调。它不是替代 WaitGroup 或 context 的工具。
- 如果只是等结束,用
chan struct{}配合close()比WaitGroup更重,且易死锁 - 如果要收集结果,优先用带缓冲的
chan Result,避免发送方阻塞 - 多个协程往同一 channel 发送时,务必确保发送总数可控(否则可能 panic:send on closed channel)
-
select默认分支不能滥用——它会让协程“忙等”,消耗 CPU
简单结果收集示意:
results := make(chan int, 3)
for i := 0; i < 3; i++ {
go func(id int) {
results <- id * id
}(i)
}
for i := 0; i < 3; i++ {
fmt.Println(<-results) // 顺序不确定,但不会漏真正难的不是启动多少协程,而是定义清楚“哪些该并行”“哪些要串行”“失败时怎么回滚”“资源怎么复用”。这些没法靠一个“执行组”抽象掉,得靠组合原语+明确契约。


















