setreaddeadline和setwritedeadline每次调用仅对下一次i/o操作生效,必须在每次read/write前重设;禁用deadline唯一安全方式是传time.time{}。

SetReadDeadline 必须每次 Read 前重设
很多人调一次 SetReadDeadline 就以为后续所有 Read() 都会受控,结果第二轮读直接卡死。这是因为该方法只对「下一次」读操作生效,用完即失效。
常见踩坑场景包括:
- HTTP 头读完后读 body 时没重设,导致阻塞
- 自定义协议分包解析(先读包头再读载荷),第二段读不设 deadline 就 hang
- 用
bufio.Reader时,只在初始化时设了SetReadDeadline,但其内部多次调用底层conn.Read(),每次都要手动重设
正确做法是:每次调用 Read() 或 reader.Read() 前,都执行一次 conn.SetReadDeadline(time.Now().Add(5 * time.Second))。传入已过期的 time.Time(比如 time.Now())会导致立刻返回 net.ErrTimeout。
禁用 deadline 的唯一安全方式是传 time.Time{}
Go 没有 DisableDeadline 方法,但标准库明确识别 time.Time{}(即 time.Time 零值)为“无截止时间”信号。这是唯一被网络栈支持的禁用方式。
错误写法示例(看似合理,实则危险):
-
conn.SetReadDeadline(time.Unix(0, 0))—— 这是有效时间点(1970-01-01),会立即超时 -
conn.SetReadDeadline(time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC))—— 同样是有效时间,不是零值
正确写法只有这一种:
conn.SetReadDeadline(time.Time{}) // 清除读 deadline
conn.SetWriteDeadline(time.Time{}) // 清除写 deadline
conn.SetDeadline(time.Time{}) // 同时清除读+写
注意:time.Time{} 等价于 time.Unix(0, 0).UTC(),但它被 Go net 包特殊处理,仅在此上下文中代表“禁用”。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
SetWriteDeadline 不是可选项,而是必须项
写超时比读超时更隐蔽:它不依赖对方是否发数据,而可能卡在内核发送缓冲区满、对端接收窗口为 0、或中间链路拥塞。不设写超时,Write() 可能 hang 几分钟甚至更久,goroutine 无法回收。
典型高危场景:
- 向弱网设备推送大体积数据(如固件、视频帧)
- RPC 客户端批量写请求体,某次
Write()卡住拖垮整个连接 - 自定义协议中连续调用多次
Write()发送字段
解决方案:每次 Write() 前都调用 conn.SetWriteDeadline(time.Now().Add(3 * time.Second))。注意 Write() 返回 n, nil 仅代表数据进入内核 buffer,不代表送达对端;超时约束的只是“进 buffer”这一步的阻塞。
SetDeadline 和分开设置读/写 deadline 的取舍
SetDeadline(t) 同时影响读和写,语义模糊,且实际业务中读写超时需求通常不同(例如读要 5 秒,写只要 1 秒)。强行共用一个 deadline 容易误伤。
推荐始终使用分离式控制:
- 用
SetReadDeadline()控制读行为(如首包握手、心跳响应) - 用
SetWriteDeadline()控制写行为(如响应生成耗时、流式推送节奏) - 避免混用
SetDeadline(),除非你明确需要读写共享同一时间点
另外,deadline 是绝对时间点,不会自动续期,也不跨操作生效——哪怕你刚读完数据,下一次读前仍需重设,这点和很多人的直觉相反。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










