如何非阻塞等待所有Go协程执行完成?附代码及输出疑问
问题分析与解决方案
你的代码核心问题是main goroutine没有等待所有子协程执行完毕就提前退出了,同时通道的设计也存在不合理之处,导致协程无法正常完成执行。我们一步步拆解问题:
为什么当前代码输出不完整?
- main函数提前终止:Go程序的生命周期由main goroutine决定,一旦main执行到
fmt.Println("Done...")后结束,所有后台运行的子协程会被强制终止,所以你看不到后续的Done with loop输出。 - 通道缓冲与接收逻辑不匹配:你创建的
ok通道缓冲只有1,当第二个及以后的协程尝试发送true时,会因为通道已满而阻塞;而main循环里的select大部分时候走default分支,并没有持续接收通道信号,导致阻塞的协程永远无法完成。 - 用
time.Sleep控制流程不可靠:靠sleep来同步协程是非常糟糕的实践,实际场景中协程的执行时间是不确定的,这种方式完全无法保证同步正确性。
最优解决方案:使用sync.WaitGroup
Go标准库提供的sync.WaitGroup是专门用于等待多个goroutine完成的工具,代码简洁且可靠:
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup fmt.Println("Starting...") for i := 0; i <= 3; i++ { wg.Add(1) // 每启动一个协程,等待计数器+1 fmt.Println("Loop: ", i) // 传递循环变量副本,避免协程共享同一个变量地址的陷阱 go func(i int) { defer wg.Done() // 协程执行完毕,计数器-1(defer确保panic时也会执行) long(i) }(i) } wg.Wait() // 阻塞等待所有协程完成,直到计数器归0 fmt.Println("Done...") } func long(i int) { fmt.Println("Inside long: ", i) time.Sleep(3 * time.Second) fmt.Println("Done with loop: ", i) }
关键说明:
wg.Add(n):在启动协程前调用,告诉WaitGroup需要等待n个协程。wg.Done():协程执行完毕时调用,减少等待计数;用defer绑定可以确保无论协程正常结束还是发生panic,都会正确通知WaitGroup。wg.Wait():阻塞main goroutine,直到所有协程都调用了Done(),计数器归0后才继续执行。
备选方案:用通道实现等待
如果你一定要用通道来完成同步,可以调整通道缓冲大小并在main中主动接收所有协程的完成信号:
package main import ( "fmt" "time" ) func main() { // 通道缓冲大小等于协程数量,避免协程发送时阻塞 ok := make(chan bool, 4) fmt.Println("Starting...") for i := 0; i <= 3; i++ { fmt.Println("Loop: ", i) go long(ok, i) } // 循环接收所有协程的完成信号 for i := 0; i <= 3; i++ { <-ok } fmt.Println("Done...") } func long(c chan bool, i int) { fmt.Println("Inside long: ", i) time.Sleep(3 * time.Second) fmt.Println("Done with loop: ", i) c <- true }
关键说明:
- 将通道缓冲设置为协程总数(4),确保每个协程发送信号时不会阻塞。
- main中通过循环接收4次通道数据,确保所有协程都完成后才继续执行。
内容的提问来源于stack exchange,提问作者gel
相关产品推荐
相关产品推荐

