net.conn 的 write 方法非并发安全,需通过 channel + 单 writer goroutine 串行化写操作;直接用 mutex 包裹或并发调用会导致数据粘连、panic 或“use of closed network connection”错误。

net.Conn 的 Write 方法不是并发安全的,多个 goroutine 直接调用会出错——必须串行化写操作,不能靠 sync.Mutex 临时包一层了事。
为什么并发 Write 会 panic 或写乱数据
Go 标准库明确不承诺 net.Conn 接口的跨协程安全性。底层如 tcpConn 没有对写路径加锁,TLS 连接或套了 bufio.Writer 时尤其容易触发 write: broken pipe、use of closed network connection,甚至直接 panic。
常见现象包括:
- 客户端收到两个 JSON 响应粘连成
{"a":1}{"b":2},解析失败 - 竞态检测(
go run -race)报出对同一内存地址的并发写 - 连接已关闭,但某个 goroutine 还在往
conn.Write里塞数据,触发异常
用 channel + 单 writer goroutine 是最稳方案
核心是把所有写请求收拢到一个 goroutine 里顺序执行,避免锁开销,也规避 Close 和 Write 的竞态。
实操要点:
- 定义
type writeReq struct { data []byte; resp chan error },支持带反馈的写入 - channel 缓冲区大小设为合理值(如
128),防发送方阻塞太久 - writer goroutine 中只做
conn.Write(req.data),编码、拼包等耗时操作提前完成 - 连接关闭前必须
close(ch)并sync.WaitGroup等 writer 退出,否则 goroutine 泄漏
示例片段:
ch := make(chan writeReq, 128)
go func() {
for req := range ch {
_, err := conn.Write(req.data)
req.resp <h3>为什么别用 sync.Mutex 包 Write</h3><p>加锁看似简单,但掩盖了更危险的问题:</p>
- 无法防止
conn.Close()和conn.Write()同时发生——Close 可能中断正在阻塞的 Write,返回use of closed network connection - 锁粒度难把握:锁整个
Write调用会严重拖慢高并发写吞吐;只锁 buffer 拼接仍解决不了底层 socket 写竞争 - 业务逻辑中可能漏锁(比如忘记锁
conn.SetWriteDeadline()前的写操作)
UDP 场景下 WriteTo 不需要额外保护,但要注意 addr 复用
UDP 的 WriteTo 是无状态的,每次调用都带完整目标地址,本身不依赖 conn 内部状态,所以不存在跨 goroutine 写冲突问题。
但必须注意:
-
WriteTo第二个参数必须是ReadFromUDP返回的*net.UDPAddr,不能是 nil、监听地址或缓存的旧地址 - 别把
addr存成全局变量或结构体字段跨 goroutine 复用——标准库为性能复用底层内存,下次ReadFromUDP可能覆盖它 - 服务端用
net.ListenUDP,客户端用net.DialUDP,二者不可混用
真正容易被忽略的是 writer goroutine 的退出时机和 channel 关闭顺序——没等 writer 消费完就 close channel,会导致部分写请求永久丢失;而 writer 不检查 channel 是否已关闭,又可能死循环。这两处必须配对处理。











