并发写 net.conn 不安全,因其 write() 无锁且非原子,多 goroutine 竞争导致写失败、panic 或静默断连;必须用单 writer goroutine + channel 串行写入,并配合超时控制、优雅关闭与重连状态同步。

直接在多个 goroutine 里对同一个 net.Conn 调用 Write(),一定会出错——不是偶尔乱序,而是确定性地写失败、panic 或连接被静默关闭。
为什么并发写 net.Conn 不安全
net.Conn.Write() 本身不加锁,也不保证原子性。多个 goroutine 同时调用,底层会竞争 socket 文件描述符的写缓冲区状态,导致:
• write: broken pipe 或 use of closed network connection 错误
• 数据被截断、拼接错乱(比如 {"id":1} 和 {"id":2} 写成 {"id":1}{"id":2} 或更糟的 {"id":1{"id":2}}
• goroutine 泄漏:写失败后没处理错误,继续往已关闭的 conn 发送
必须用单 writer goroutine + channel 中转
这不是“推荐做法”,而是唯一生产可用的模式。所有业务逻辑把要发的数据塞进一个带缓冲的 channel,由专属 goroutine 串行消费并调用 conn.Write():
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- channel 类型建议定义为
chan []byte或chan *WritePacket,避免频繁分配 - 缓冲大小设为 64 或 128,太小容易阻塞业务,太大占用内存
- writer goroutine 必须监听
conn.Close()或ctx.Done(),及时退出,别让 channel 堵死 - 不要在 writer 里做序列化(如
json.Marshal),提前做好,否则 CPU 拖慢整个写队列
写超时和连接关闭必须协同处理
只靠 channel 不能解决网络异常。writer goroutine 必须配合 conn.SetWriteDeadline():
- 每次
Write()前设置,比如conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) - 遇到
net.ErrTimeout或os.IsTimeout()错误,立刻关闭连接、清空 channel、退出 goroutine - 别在外部另起 goroutine 调
conn.Close()—— writer 自己关最安全,避免 double-close - 如果用了
gorilla/websocket,禁用其默认SetPongHandler,自己统一用 ticker 批量检查连接活性,否则每个连接绑一个 timer,百万连接时 runtime.timer 直接打爆
客户端重连时的写队列清理很关键
重连成功后,旧连接的 writer goroutine 可能还在往已关闭的 conn 写,而新连接的写 channel 还没清空旧数据:
- 用
sync.Once包裹conn.Close(),确保只关一次 - writer goroutine 退出前,用
close(ch)让所有发送方收到 closed channel panic,而不是一直阻塞 - 业务层发消息前先检查连接状态(比如读取一个
atomic.Bool),避免往已标记断开的连接发数据 - 若需消息不丢,channel 里存的是指针或结构体,带上重试标记和序列号,断线重连后可从断点续发
真正难的不是写通道,而是怎么让写失败时的状态(断连、重连、丢包)和业务逻辑对齐——这里没有通用解,得按协议语义自己编排。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










