goroutine 是 go 运行时调度的轻量执行单元,main 退出即强制终止所有 goroutine;需用 sync.waitgroup 显式等待,add 必须在 go 前调用,注意闭包变量陷阱。

goroutine 不是线程,也不是“后台任务”的代名词——它只是 Go 运行时调度的、可被快速创建和销毁的执行单元。你不需要“管理”它,但必须理解它何时会消失、何时会卡住、以及为什么 main 函数退出后其他 goroutine 全部被强制终止。
main 函数退出就 kill 所有 goroutine,没商量
这是最常被忽略的致命事实:main 函数返回那一刻,整个进程立即退出,所有正在运行的 goroutine 被硬杀,不执行 defer、不关闭 channel、不释放资源。
- 错误写法:
go doWork(); time.Sleep(100 * time.Millisecond)—— 依赖时间猜测,不可靠 - 正确做法:用
sync.WaitGroup显式等待完成 - 注意
WaitGroup.Add()必须在go语句之前调用,否则可能漏计数 - 如果
goroutine内部会 panic,defer wg.Done()仍能保证计数正确(因为 defer 在 panic 前执行)
for 循环里启动 goroutine,闭包变量陷阱
下面这段代码几乎必出错:
for i := 0; i <p>原因:所有匿名函数共享同一个变量 <code>i</code> 的地址,循环结束时 <code>i == 5</code>,而 <code>goroutine</code> 启动是异步的,大概率读到最终值。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/ai/4226" title="小红书创作服务平台"><img src="https://img.php.cn/upload/ai_manual/001/246/273/178599616487947.png" alt="小红书创作服务平台" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/ai/4226" title="小红书创作服务平台" class="overflowclass">小红书创作服务平台</a> <p class="overflowclass">小红书创作服务平台是一款AI文本写作工具,一站式创作者服务工作平台。</p> </div> <a rel="nofollow" href="/ai/4226" title="小红书创作服务平台" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 修复方式 1:传参捕获当前值:
go func(val int) { fmt.Println(val) }(i) - 修复方式 2:在循环内声明新变量:
for i := 0; i - Go 1.22+ 在 for range 中已默认按值绑定迭代变量,但普通 for 仍需手动处理
channel 关闭前必须确保没人再发
close(ch) 只能由发送方调用,且只能调用一次;向已关闭的 channel 发送数据会 panic:send on closed channel。
- 常见错误:多个
goroutine同时往同一 channel 发,谁来 close?没人协调就容易 panic - 推荐模式:用单个 sender(或用
sync.Once包裹 close),receiver 用for range安全接收 - 不要靠
len(ch) == 0判断是否该 close —— channel 长度不反映是否还有待发送数据 - 带缓冲 channel 的
len()返回当前队列长度,cap()返回容量,二者都与关闭状态无关
goroutine 泄漏:没接收的 send 或没发送的 recv
无缓冲 channel 的发送操作会阻塞,直到有 goroutine 接收;反之亦然。一旦逻辑出错,就卡死,goroutine 永远无法退出。
- 典型泄漏场景:
ch := make(chan int); go func() { ch —— 没人接收,这个 goroutine 永远挂起 - 调试技巧:用
runtime.NumGoroutine()日志观察数量是否持续增长 - 生产环境建议加超时:
select { case ch - 别依赖
time.After单独做超时控制,要配合select使用
真正的难点不在语法,而在对“生命周期”的判断:哪个 goroutine 负责关 channel、谁等谁、谁该超时、谁负责清理。这些没法靠编译器检查,得靠设计时的明确约定。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










