net.Conn非跨协程安全,多goroutine并发Write会导致数据错乱、截断或panic;推荐方案是用channel将写请求串行交由单个writer goroutine执行。

Go里net.Conn本身不是跨协程安全的
直接在多个 goroutine 中并发调用同一个 net.Conn 的 Write 方法,会导致数据错乱、写入截断甚至 panic(尤其在 TLS 连接或使用 bufio.Writer 时)。这不是 Go 的 bug,而是设计使然:net.Conn 接口不承诺线程安全,底层实现(如 tcpConn)也未对读写加锁。
常见错误现象包括:
- 客户端收到拼接混乱的响应(如两个
Write写出的 JSON 对象粘连成一个非法字符串) -
write: broken pipe或use of closed network connection在非预期时机出现 - 启用
GOEXPERIMENT=fieldtrack或竞态检测(go run -race)时明确报出写竞争
最常用且推荐的方案:用单个 goroutine 串行写
核心思路是把所有写请求通过 channel 发给一个专属 writer goroutine,由它按序调用 conn.Write。这既避免锁开销,又保证顺序和完整性。
实操建议:
- 定义
type writeReq struct { data []byte; resp chan error },支持带反馈的写请求 - writer goroutine 中用
for req := range ch消费,直接_, err := conn.Write(req.data),再req.resp - 不要在 writer 中做耗时操作(如 JSON 编码),提前在发送方完成,只传
[]byte - 务必在连接关闭时 close channel 并等待 writer 退出,否则 goroutine 泄漏
ch := make(chan writeReq, 128)
go func() {
for req := range ch {
_, err := conn.Write(req.data)
req.resp <- err
}
}()为什么不用 sync.Mutex 包一层?
加互斥锁看似简单,但实际会掩盖更深层的问题:
- 无法防止多个 goroutine 同时调用
Close()和Write()——Close会中断正在阻塞的Write,返回use of closed network connection - 锁粒度难把握:如果锁住整个
Write调用,高并发下写吞吐骤降;若只锁 buffer 拼接,仍无法解决底层 socket 写竞争 - 与
net/http等标准库行为不一致 —— 它们内部也是走单 writer 模式,强行加锁反而增加维护成本
简言之:sync.Mutex 解不了根本问题,还容易写出“看似能跑、线上崩得慢”的代码。
注意 net.Conn.SetWriteDeadline 的协程安全性
SetWriteDeadline 是并发安全的,可以被任意 goroutine 调用,但它只影响**后续**的阻塞写操作。如果 writer goroutine 正在 Write 中阻塞,此时另一个 goroutine 调用 SetWriteDeadline,不会中断当前阻塞,也不会触发超时 —— 超时逻辑只在下一次系统调用时生效。
所以:
- 不要依赖
SetWriteDeadline来“取消”正在进行的写操作 - 若需强制中断写,应关闭连接(
conn.Close()),writer goroutine 需监听conn.Read或conn.Write返回的错误来退出 - deadline 应在 writer goroutine 内部统一设置(比如每次
Write前调用),避免跨协程时间窗口不一致
跨协程写 socket 最容易被忽略的点,是以为“只要没 panic 就安全”——数据错乱往往静默发生,直到业务层解析失败才暴露。真要保序保完整,别绕开 channel + 单 writer 这个模式。



















