
Go 程序中协程(goroutine)因主函数过早退出而被强制终止,导致通道写入中断、数据丢失;正确做法是使用 sync.WaitGroup 同步协程生命周期,而非依赖不可靠的 time.Sleep。
go 程序中协程因主函数过早退出而被强制终止,导致通道写入中断、数据丢失;正确做法是使用 sync.waitgroup 同步协程生命周期,而非依赖不可靠的 time.sleep。
你的程序只输出到第 8 次添加 URL 就停止,并非 addUrls 函数本身出错,而是 main 函数在 goroutine 还未执行完毕时就已退出 —— Go 程序会在 main 函数返回后立即终止所有 goroutine,无论它们是否仍在运行。
根本原因在于你使用了 time.Sleep(time.Millisecond * 100) 来“等待”协程启动,但这既不精确也不可靠:
-
100ms远不足以让addUrls(含 5000 次Sleep(10ms),理论耗时 ≥50s)完成; - 更严重的是,
main在启动consumeUrls后立刻结束,此时两个 goroutine 都被强制杀死,甚至consumeUrls只执行了一次(读取一个值后即终止),而addUrls仅来得及写入前 9 个(因fmt.Printf和通道操作存在微小延迟,实际写入 9 次后main已退出)。
✅ 正确解法:使用 sync.WaitGroup 显式等待所有 goroutine 完成:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
urlHolder := make(chan string, 5000)
var wg sync.WaitGroup
fmt.Printf("Starting program\n")
// 启动 addUrls 并登记到 WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
addUrls(urlHolder)
}()
// 启动 consumeUrls 并登记(注意:需循环消费,否则只处理一次)
wg.Add(1)
go func() {
defer wg.Done()
consumeUrls(urlHolder)
}()
// 等待所有 goroutine 完成
wg.Wait()
fmt.Printf("Done\n")
}
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/skill6460" title="Golang Naming"><img
src="https://img.php.cn/upload/skill/000/000/081/179094616043400.jpg" alt="Golang Naming" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="overflowclass">Golang Naming</a>
<p class="overflowclass">Go(Golang)命名规范 — 包括包、构造函数、结构体、接口、常量、枚举、错误、布尔值、接收器、getter/setter、函数等。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
-
sync.WaitGroup替代time.Sleep:wg.Add(1)标记任务数,defer wg.Done()确保 goroutine 完成时计数减一,wg.Wait()阻塞直到全部完成; -
consumeUrls改为循环消费:原函数只读取一次,现用for url := range urlHolder持续读取直至通道关闭; -
显式关闭通道:
addUrls结束后调用close(urlHolder),使range能正常退出,避免死锁; -
匿名 goroutine 包裹:便于在内部调用
defer wg.Done(),确保异常时也能释放计数。
⚠️ 注意事项:
- 切勿用
time.Sleep控制 goroutine 生命周期 —— 它无法应对不同环境下的调度差异; - 若通道未关闭且消费者未退出,
range会永久阻塞;务必保证生产者明确关闭通道; -
WaitGroup的Add必须在go语句之前调用,否则存在竞态风险。
通过以上重构,程序将稳定完成全部 5000 次 URL 添加与消费,输出完整且可预期。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










