必须手动调用 cancel() 桥接 TCP 断开与 Context 取消,因 net.Conn 不感知 context;读到 io.EOF 后需检查 ctx.Err(),为 nil 时主动 cancel,否则 goroutine 泄漏。

不能靠 conn.Close() 触发 Context 取消,必须手动同步信号
Go 的 net.Conn 本身不感知 context.Context,断开连接(如对端关闭、网络中断)只会让下一次 Read() 或 Write() 返回 io.EOF 或 net.ErrClosed,而不会自动关闭 ctx.Done()。如果你指望“连接断开 → ctx.Done() 关闭 → goroutine 退出”,那会漏掉所有未触发读写的阻塞状态,导致 goroutine 泄漏。
为什么监听 conn.Read() 错误后不能直接 return?
常见错误是这样写:
for {
n, err := conn.Read(buf)
if err != nil {
// 错误:这里只判断 err,没检查 ctx 是否已取消
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
return // 直接退出,但可能 ctx 已被 cancel,却没响应
}
log.Printf("read error: %v", err)
continue
}
// 处理数据...
}
- 连接断开时返回
io.EOF是正常行为,不代表请求已完成或可安全清理资源;服务端需结合ctx.Err()判断是否该终止整个会话 - 如果此时
ctx.Err()已是context.Canceled,说明上层已主动放弃该连接,应立即释放关联资源(如 channel、timer、DB 连接池句柄) - 若仅依赖连接错误退出,而忽略
ctx.Err(),会导致超时/取消逻辑失效,尤其在客户端快速重连又断开的场景中极易堆积 goroutine
如何让 TCP 断开与 Context 取消真正联动?
核心思路:把连接状态变化作为「取消源」之一,显式调用 cancel(),而非等待系统调用返回错误后再响应。
- 在读 goroutine 中,每次
Read()返回io.EOF或net.ErrClosed后,先检查ctx.Err() == nil;若为nil,说明是意外断开,此时应调用cancel()主动通知写 goroutine 和其他子任务 - 写 goroutine 必须监听同一
ctx.Done(),并在退出前确保不再向conn.Write()发送数据(避免 panic) - 不要复用同一个
cancel函数:若连接重建,必须新建context.WithCancel(),否则旧cancel()可能误关新连接上下文 - 建议封装一个
safeCloseConn(conn net.Conn, cancel context.CancelFunc),内部先conn.Close()再调用cancel(),并加sync.Once防重复调用
读写 goroutine 如何协同退出不丢数据?
典型长连接至少有两个 goroutine:一个读循环解包,一个写循环从 channel 拉数据发送。二者共享同一个 ctx,但退出顺序很关键。
- 读 goroutine 收到
io.EOF且ctx.Err() == nil时,应立即调用cancel()并 close 写 channel(如close(writeCh)),通知写 goroutine “不再接收新消息” - 写 goroutine 在循环中需同时 select
ctx.Done()和writeCh,并在收到ctx.Done()后尝试 flush 剩余缓冲(如有),再退出 - 主 goroutine 应用
sync.WaitGroup等待两者退出,**不能**仅靠conn.Close()后就认为连接已清理完毕 - 所有涉及
conn.Read()/conn.Write()的地方,都必须配对使用conn.SetReadDeadline()和conn.SetWriteDeadline(),deadline 值由time.Now().Add(200 * time.Millisecond)动态计算,而非固定值——否则在网络抖动时会掩盖真实断开信号
最易被忽略的一点:Context 取消和 TCP 断开是两个独立事件源,ctx.Done() 不代表连接已关,io.EOF 也不代表 ctx 已取消。必须用 cancel 函数桥接二者,且 cancel 只能调用一次。任何试图“重用 cancel”或“在多个 goroutine 里并发调用 cancel”的做法,都会破坏 Context 树的级联语义。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











