Go程序在Goroutine完成前退出,如何正确阻塞并关闭Channel?
问题根源与解决方案
你的代码核心问题是主goroutine提前退出——主函数启动所有goroutine后,直接打印donezo就结束了。而Go程序的规则是:只要主goroutine退出,所有其他后台goroutine都会被强制终止,所以你看不到任何任务执行的输出。加time.Sleep只是临时让主goroutine多存活一段时间,给其他goroutine留了执行窗口,但这是治标不治本的办法。
同时你现有逻辑存在循环依赖:你试图让worker完成后关闭通道,但worker的for range ch循环只有在通道被关闭时才会退出,这就形成了"worker等通道关闭,通道等worker完成"的死锁闭环(如果主goroutine不提前退出的话)。
正确实现范式
要优雅完成「任务生产→worker消费→主goroutine等待全部完成」的流程,需要分两步:
- 等待所有任务生产者(
dogoroutine)完成任务并将结果发送到通道,此时可以安全关闭结果通道; - 等待所有消费者worker处理完通道内的所有结果,再让主goroutine退出。
修正后的代码
package main import ( "fmt" "sync" "time" ) // 模拟耗时任务 func do(num int, ch chan<- int, wg *sync.WaitGroup) { defer wg.Done() time.Sleep(500 * time.Millisecond) ch <- num } // worker处理结果 func worker(id int, ch <-chan int, wg *sync.WaitGroup) { defer wg.Done() fmt.Println("starting worker", id) for i := range ch { fmt.Println("worker", id, "processed:", i) } } func main() { results := make(chan int) var wgJobs sync.WaitGroup var wgWorkers sync.WaitGroup // 启动53个任务生产者 for i := 0; i < 53; i++ { wgJobs.Add(1) go do(i, results, &wgJobs) } // 启动3个worker消费者 for i := 0; i < 3; i++ { wgWorkers.Add(1) go worker(i, results, &wgWorkers) } // 单独goroutine:等所有任务完成后关闭通道 go func() { wgJobs.Wait() close(results) }() // 主goroutine等待所有worker处理完结果 wgWorkers.Wait() fmt.Println("donezo") }
关键修正点
- 新增
wgJobs等待所有任务完成:每个dogoroutine启动时调用wgJobs.Add(1),任务结束时defer wgJobs.Done(),确保所有结果都发送到通道后再关闭通道; - 用独立goroutine关闭通道:避免主goroutine阻塞在任务等待逻辑上,无法启动worker;
- 主goroutine最后等待
wgWorkers.Wait():确保所有worker处理完通道内的所有结果后,才执行退出逻辑。
内容的提问来源于stack exchange,提问作者b0bu
相关产品推荐
相关产品推荐

