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 <h3>为什么不用 <code>sync.Mutex</code> 包一层?</h3><p>加互斥锁看似简单,但实际会掩盖更深层的问题:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件"><img
src="https://img.php.cn/upload/webcode/000/000/164/636a2b4d84031727.png" alt="使用Go语言搭建家庭相册系统-相关课件" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</a>
<p class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</p>
</div>
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 无法防止多个 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 这个模式。










