
go程序中goroutine意外退出,往往是因为main函数过早结束导致整个进程退出,而非goroutine自身逻辑错误;本文通过分析典型channel通信案例,详解如何使用sync.waitgroup实现可靠同步,并修正消费端逻辑缺陷。
go程序中goroutine意外退出,往往是因为main函数过早结束导致整个进程退出,而非goroutine自身逻辑错误;本文通过分析典型channel通信案例,详解如何使用sync.waitgroup实现可靠同步,并修正消费端逻辑缺陷。
你的Go程序只输出了9次“url added”(索引0–8)就停止,根本原因并非addUrls函数出错,而是main函数执行完毕后立即退出进程,所有正在运行的goroutine(包括尚未完成的addUrls和仅执行了一次的consumeUrls)被强制终止。
在原始代码中:
-
go addUrls(urlHolder)启动协程后,main紧接着调用time.Sleep(100ms)—— 这段休眠远不足以让5000次带10ms延迟的写入完成(理论需50秒); - 随后启动
consumeUrls,但它仅从channel接收一次就返回,未循环消费; - 最后
fmt.Printf("Done")执行完毕,main函数退出,整个程序终结。
✅ 正确做法:使用 sync.WaitGroup 同步生命周期
WaitGroup 是Go标准库中专为等待一组goroutine完成而设计的同步原语,避免依赖不可靠的time.Sleep:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
urlHolder := make(chan string, 5000)
var wg sync.WaitGroup
// 启动生产者,计数+1
wg.Add(1)
go func() {
defer wg.Done() // 完成时减1
addUrls(urlHolder)
}()
// 启动消费者,计数+1
wg.Add(1)
go func() {
defer wg.Done()
consumeUrls(urlHolder)
}()
// 等待所有goroutine完成
wg.Wait()
fmt.Println("Done")
}
func addUrls(urlHolder chan string) {
myurl := "https://example.com/"
for i := 0; i <h3>⚠️ 关键改进说明:</h3>
-
sync.WaitGroup替代time.Sleep:精确控制main退出时机,确保所有goroutine自然完成; -
close(urlHolder)+for range:显式关闭channel,使消费者能通过range自动感知结束,避免死锁或漏消费; -
匿名goroutine封装:便于
defer wg.Done()统一管理完成状态; -
移除全局变量
urlCount:改用局部变量提升并发安全性(原代码中urlCount++无同步保护,存在竞态风险)。
? 额外提醒:
- 原
consumeUrls函数仅接收一次即返回,属于逻辑缺陷,非同步问题——即使main不退出,它也只会打印1个URL; - channel容量为5000虽能暂存全部URL,但若消费者过慢,生产者仍会在第5001次写入时阻塞(本例因
main早退未暴露该问题); - 实际项目中,应结合
context控制超时与取消,而非依赖固定Sleep。
遵循上述模式,即可写出健壮、可预测的Go并发程序。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











