长连接 handler 必须将 context.context 作为第一参数传入,否则 ctx.done() 无法中断阻塞 i/o,导致协程卡死;http 用 r.context()、grpc stream 用 stream.context();子 goroutine 需传递 ctx 并显式释放资源;阻塞读写必须用 select + ctx.done()。

长连接 handler 必须把 context.Context 当第一参数传入
不这么做,ctx.Done() 就进不到真正阻塞的 I/O 里,协程会卡死成僵尸。HTTP handler 用 r.Context(),别自己 new context.Background();gRPC stream 直接用 stream.Context(),它已集成客户端断连信号。
常见错误:func handleConn(conn net.Conn) { ... } —— 完全脱离 context 控制;正确写法是 func handleConn(ctx context.Context, conn net.Conn) { ... },且所有子 goroutine 都要接收并传递这个 ctx。
注意:子 goroutine 里不能只监听 ctx.Done() 就 return,还得显式关闭 conn、释放 buffer pool、cancel db transaction 等——GC 不管这些。
阻塞读写必须用 select + ctx.Done(),不能裸调 Read/Write
conn.Read() 或 stream.Recv() 是阻塞调用,不加 context 监听,cancel 信号永远收不到。写成 for { n, _ := conn.Read(buf) } 就等于放弃控制权。
必须配合 select:
select {
case n, err := conn.Read(buf):
// 处理数据
case <p>更稳妥的做法是结合 <code>conn.SetReadDeadline()</code> 和 <code>ctx.Deadline()</code>,避免 Read 卡在内核态(尤其在反向代理后);对 <code>http.ResponseWriter</code> 写流也一样,每次 <code>Flush()</code> 前检查 <code>ctx.Err() != nil</code>。</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><h3>gorilla/<a style="color:#f60; text-decoration:underline;" title="websocket" href="https://m.php.cn/zt/16870.html" target="_blank">websocket</a> 的 Ping/Pong 必须手动启用</h3><p>默认不发 Ping,也不自动响应 Pong。<code>SetPongHandler(nil)</code> 不等于保活,只是关掉默认处理;你得自己启一个 goroutine 定期调 <code>conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(writeWait))</code>。</p><p>关键点:</p>
-
SetPongHandler要重置conn.SetReadDeadline(),否则一次 Pong 后超时不会更新 -
pingPeriod应设为pongWait * 0.9,比如pongWait = 55 * time.Second,那 ping 就得 ≤ 49s 发一次 -
writeWait单独设(推荐 10s),防止WriteMessage卡死拖垮整个 goroutine - 所有超时值必须严格小于 Nginx 的
proxy_read_timeout(常见 60s),否则中间设备先断,服务端还在等
连接管理 map 并发写 panic 是高频翻车点
直接 map[string]*websocket.Conn 或 map[uint64]*net.Conn 并发读写必 panic:“concurrent map read and map write”。
安全做法:
- 用
sync.RWMutex包一层,读多写少时开销极小 - key 用
uint64(如原子自增 ID),别用 string,省哈希和 GC 压力 - value 里嵌
sync.Once和atomic.Bool,防多个 goroutine 同时触发清理逻辑 - 连接关闭时,必须从 map 删除 + 关闭
conn+ cancel 子ctx,三者缺一不可
最易被忽略的是:连接异常关闭(比如 io.EOF、websocket.CloseAbnormalClosure)时,reader/writer goroutine 若没覆盖这些 error 分支,就会永远卡在 ReadMessage() 或 select 上,状态显示 [IO wait],但实际已不可回收。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










