net.conn 不能跨 goroutine 复用,因其读写操作非线程安全,多 goroutine 并发调用 read/write 会导致数据错乱、io.eof 异常返回或 panic;应为每个连接绑定独立 goroutine,统一处理读、写、超时与关闭,并通过 channel 协调响应式写入。

为什么 net.Conn 不能跨 goroutine 复用?
因为 net.Conn 的读写操作本身不是线程安全的——多个 goroutine 同时调用 conn.Read() 或 conn.Write(),会触发未定义行为,常见表现为数据错乱、io.EOF 突然返回、甚至 panic(比如 “use of closed network connection” 在没显式关闭时就出现)。
每个连接配一个 goroutine 的本质,是把该连接的生命周期和一个 goroutine 绑定:读、写、超时、关闭都在同一个逻辑流里完成。这不是“为了并发而并发”,而是规避 net.Conn 的底层限制。
- 别在多个 goroutine 里共享一个
conn变量去读或写 - 如果要响应式写(比如收到 A 消息后主动推 B 数据),用 channel 把写请求发给专属的处理 goroutine,而不是直接调用
conn.Write() -
conn.SetReadDeadline()和conn.SetWriteDeadline()必须在调用对应 I/O 前设置,且每次调用前都得重设——它不自动续期
怎么安全地终止一个连接 goroutine?
靠 conn.Close() 触发读写错误,再配合 select + context 控制退出时机。单纯用 return 或 os.Exit() 会导致连接残留、文件描述符泄漏。
典型错误是:goroutine 正在 conn.Read() 阻塞,你从外面调了 conn.Close(),但没检查返回值,也没做清理,结果 goroutine 卡住或继续跑空循环。
立即学习“go语言免费学习笔记(深入)”;
- 所有
Read()和Write()必须检查 error,遇到io.EOF或net.ErrClosed就该退出 goroutine - 用
context.WithTimeout()或context.WithCancel()包裹业务逻辑,select中监听ctx.Done(),退出前关连接 - 别依赖
defer conn.Close()放在函数开头——goroutine 可能永远等不到 defer 执行
for { conn.Read() } 循环里要不要加 time.Sleep()?
绝对不要。TCP 是流式协议,conn.Read() 在阻塞模式下会挂起 goroutine 直到有数据、对端关闭或超时;加 time.Sleep() 只会让响应变慢、吞吐下降,还掩盖了真正的问题(比如没设 deadline、没处理粘包)。
唯一需要 sleep 的场景是:你在轮询非阻塞连接(极少见),或者做重连退避——但这不属于“每个连接一个 goroutine”的正常路径。
- 阻塞读本身就是 goroutine 友好的:没数据时它让出调度权,不占 CPU
- 如果发现 CPU 100%,问题大概率出在 Read 返回 0 字节没判断、或错误被忽略后进了死循环
- 粘包不是靠 sleep 解决的,得用定长头、分隔符或 TLV 解析——否则 sleep 只是碰运气等齐数据
高并发下为什么文件描述符很快耗尽?
Linux 默认单进程最多打开 1024 个文件描述符,而每个 TCP 连接至少占用 1 个 fd。当连接数接近上限,accept() 会开始返回 "too many open files" 错误,新连接被拒绝。
这不是 Go 的问题,是 OS 层限制。Goroutine 轻量,但 fd 是内核资源,不会因为用了 Go 就自动扩容。
- 启动前用
ulimit -n 65536提升上限(需 root 权限或配置/etc/security/limits.conf) - 务必确保每个连接 goroutine 最终都执行了
conn.Close(),尤其在错误分支里(比如解析失败、认证失败) - 用
lsof -p <pid> | wc -l</pid>实时观察 fd 数量,比看连接数更准——有些连接可能已断开但 fd 没关










