conn.Read()和conn.Write()在高并发下卡住是因未设deadline或未解耦读写goroutine;须每次Read前调SetReadDeadline,写操作需用带缓冲channel解耦。

conn.Read() 和 conn.Write() 在高并发下卡住,不是 bug,是没设 deadline 或没解耦读写 goroutine —— 本质是 socket 缓冲区满/空 + 阻塞 I/O 模式共同导致的永久挂起。
每次 conn.Read() 前必须重置 SetReadDeadline()
Go 的 net.Conn 默认阻塞,Read() 会一直等,直到有数据、连接关闭或出错。客户端静默断连或网络抖动时,goroutine 就永远停在 net.(*conn).Read,pprof 看似“卡死”,实为泄漏。
- 必须在每次调用
conn.Read()前设置:conn.SetReadDeadline(time.Now().Add(30 * time.Second)) - 不能复用旧时间值,否则 deadline 越来越早,连接被误杀
- HTTP/1.1 keep-alive 场景可只设一次(但得确保首次设置成功)
- 错误类型是
*net.OpError,用os.IsTimeout(err)判断,别字符串匹配"i/o timeout"
写操作必须用带缓冲 chan []byte 解耦
多个 goroutine 直接调 conn.Write() 会竞争底层 socket 状态,轻则 panic,重则数据错乱。标准解法是:一个 goroutine 专读,一个专写,中间用 channel 传数据 —— 但 channel 若无缓冲,写 goroutine 一卡,整个连接就瘫痪。
- 用
make(chan []byte, 16)(容量 ≥ 16,按消息平均大小和突发流量调) - 读 goroutine 发送前加
select { case writeChan ,避免阻塞 - 写 goroutine 取出后立刻
conn.Write(),并配conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) - 检查
n != len(data),未写全需重试或记录错误,不能忽略
binary.Write() 卡住?大概率是 Nagle + 缓冲区积压 + 生命周期不同步
跨设备(如 Raspberry Pi ↔ MacBook)场景下,binary.Write(conn, &msg) 高频调用后突然阻塞,常因三重叠加:
- TCP Nagle 算法启用(
TCP_NODELAY关闭),小包合并等待 ACK,服务端读取慢时发送缓冲区持续积压 - 写 goroutine 生命周期未与连接绑定,主 goroutine 过早退出,连接半关闭,内核级阻塞无法被 Go deadline 捕获
-
binary.Write()内部调conn.Write(),但没设写超时,也没检查返回长度,误以为“已发出去” - 解决方案:显式
conn.SetNoDelay(true)关 Nagle;用context.WithCancel统一管理连接生命周期;所有Write后必查n
别碰非阻塞 socket,Go 的 net.Conn 不需要
有人想用 accept4(..., SOCK_NONBLOCK) 或 ioctlsocket(..., FIONBIO) 手动切非阻塞模式,这在 Go 里是弯路。Go 的 net.Conn 抽象层不暴露底层 socket 控制权,且 runtime 网络轮询器(netpoll)本身已基于 epoll/kqueue 实现异步 I/O —— 你只需用好 deadline + channel + goroutine 职责分离,就能获得非阻塞语义。
强行设非阻塞反而破坏 Go 运行时调度逻辑,Read() 会频繁返回 syscall.EAGAIN,你得自己循环重试,还绕过 SetReadDeadline 机制,得不偿失。真正要控的是行为,不是模式。


















