
Go 程序中协程提前退出,通常是因为主函数(main)在子协程完成前就结束了;使用 time.Sleep 临时等待不可靠,应改用 sync.WaitGroup 实现精确同步。
go 程序中协程提前退出,通常是因为主函数(main)在子协程完成前就结束了;使用 time.sleep 临时等待不可靠,应改用 sync.waitgroup 实现精确同步。
你的程序只输出了 9 次添加日志(0–8),随后立即打印 "Done" 并退出,根本原因在于:main 函数执行完毕后,整个进程立即终止,所有仍在运行的 goroutine(包括 addUrls 和 consumeUrls)被强制杀死——无论它们是否完成工作。
你当前依赖 time.Sleep(time.Millisecond * 100) 让 main 稍作等待,但这既不健壮也不可预测:100ms 远不足以让 5000 次带 10ms 延迟的发送完成(理论需 ≥50s),且 consumeUrls 本身只消费一次便退出,未循环读取通道,进一步加剧了数据积压与逻辑断裂。
✅ 正确做法是使用 sync.WaitGroup 显式管理 goroutine 生命周期:
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()
addUrls(urlHolder)
}()
// 启动消费者,计数 +1
wg.Add(1)
go func() {
defer wg.Done()
consumeUrls(urlHolder)
}()
fmt.Println("Starting program")
// 主 goroutine 阻塞等待所有任务完成
wg.Wait()
fmt.Println("Done")
}
func addUrls(urlHolder chan string) {
myurl := "https://example.com/"
for i := 0; i <p>? 关键改进说明:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill4918" title="Golang Spf13 Viper"><img
src="https://img.php.cn/upload/skill/000/000/081/179025319165074.jpg" alt="Golang Spf13 Viper" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill4918" title="Golang Spf13 Viper" class="overflowclass">Golang Spf13 Viper</a>
<p class="overflowclass">Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。</p>
</div>
<a rel="nofollow" href="/xiazai/skill4918" title="Golang Spf13 Viper" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
-
sync.WaitGroup替代Sleep:wg.Add(1)标记任务,defer wg.Done()确保完成时自动减计数,wg.Wait()阻塞直到计数归零,彻底消除竞态与超时风险; -
通道关闭与
range消费:addUrls结束前调用close(urlHolder),使consumeUrls中的for url := range urlHolder能自然退出,避免死锁或漏消费; -
匿名 goroutine 封装:避免直接传函数名导致
wg.Done()调用时机不可控(如函数 panic 时defer不执行),封装后确保defer wg.Done()总被执行; -
移除全局变量
urlCount:改用局部变量count,避免并发读写冲突(原代码中urlCount++在无锁情况下非线程安全)。
⚠️ 注意事项:
- 切勿依赖
time.Sleep控制 goroutine 执行顺序或生命周期——它无法保证逻辑完整性,仅适用于调试延时; - 向带缓冲通道发送数据不会阻塞,但若消费者过慢,缓冲区满后
addUrls将阻塞,此时需合理设计缓冲大小或采用背压机制; -
main函数返回即程序退出,所有 goroutine 强制终止,这是 Go 的确定性行为,必须主动同步。
掌握 WaitGroup 与通道生命周期管理,是编写可靠并发 Go 程序的第一道基石。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










