应使用 context.withcancel 控制长期 goroutine 生命周期:主协程调用 cancel() 后,ctx.done() 关闭,监听它的 select 立即响应;需避免使用 context.background();优先选用支持 context 的阻塞操作版本;每个阻塞点都须与 ctx.done() 一同在 select 中监听。

用 context.WithCancel 控制长期 goroutine 的生命周期
长期运行的后台 goroutine(比如日志轮转、健康检查、定时上报)必须能响应外部退出信号,否则进程无法干净关闭。最稳妥的方式是用 context.WithCancel 创建可取消上下文,并在 goroutine 内部用 select 监听 ctx.Done()。
关键点:
- 主 goroutine 调用
cancel()后,ctx.Done()返回的 channel 会立即关闭,所有监听它的select都能立刻感知 - 不要把
context.Background()直接传给长期 goroutine——它永远不会关闭,等于放弃控制权 - 如果 goroutine 内部调用了
http.Client.Do或net.Conn.Read等阻塞操作,优先选用带Context参数的版本(如client.Do(req.WithContext(ctx))),避免卡死在系统调用里
goroutine 内部必须在每个阻塞点监听退出信号
只在循环开头监听 ctx.Done() 是不够的。一旦 goroutine 进入 time.Sleep、chan recv 或网络 I/O,它就不再检查上下文,可能永远卡住。
正确做法是把所有可能阻塞的操作都放进 select,和 ctx.Done() 并列:
select {
case <p>常见陷阱:</p>
- 用
default分支做轮询:CPU 空转,且延迟响应退出 - 只监听一次
ctx.Done(),之后就进入纯阻塞调用(如conn.Read()),完全失去响应能力 - 忘记处理
ctx.Err()——比如context.Canceled和context.DeadlineExceeded需要不同清理逻辑
别用 close(done) 当作通用退出机制
虽然 close(done) 能让 立即返回零值,但它不适用于需要精确控制退出时机的场景。问题在于:
- 关闭 channel 后,所有监听者都会“同时”收到信号,但没人知道谁先读、谁后读、谁还没开始监听
- 如果 goroutine 还没执行到
select就被 close,它可能根本没机会响应 - 无法区分“主动关闭”和“channel 已关闭但未发信号”的状态,容易误判
更安全的做法是用带缓冲的 chan struct{} 发送一个值:
stop := make(chan struct{}, 1)
stop <p>但除非你有极简需求(比如单个独立 worker),否则还是推荐统一走 <code>context.Context</code>——它天然支持嵌套、超时、取消传播,且 <code>Done()</code> 是只读 channel,不会因重复 close panic。</p><h3>必须等 goroutine 真正退出,不能只发信号就完事</h3><p>发完 <code>cancel()</code> 或 <code>stop 后,如果主 goroutine 立即退出,后台 goroutine 可能还在跑,资源继续泄漏。必须同步等待它们结束。</code></p><p>标准做法是配合 <code>sync.WaitGroup</code>:</p>
- 启动前
wg.Add(1) - goroutine 结束前
defer wg.Done() - 主流程调用
wg.Wait()确保全部退出
最容易被忽略的是:WaitGroup 的计数必须和实际 goroutine 数量严格一致。漏掉 Add 或多调 Done 都会导致死锁或 panic。如果 goroutine 启动逻辑复杂(比如动态增减),建议改用 errgroup.Group,它内置了 WaitGroup 和错误传播能力。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











