主goroutine退出会导致所有子goroutine立即终止;需用sync.waitgroup或select{}阻塞main,for循环启goroutine要防变量捕获,高并发连接须用worker pool限流,并通过context优雅关闭。

主 goroutine 退出后,所有子 goroutine 立即终止——这是并发连接管理失效的最常见原因,不是代码没写对,而是没等完。
goroutine 连接没反应?先看 main 是否提前退出
HTTP server、长连接客户端、心跳协程启动后“一闪而过”,根本没处理请求,大概率是 main 函数执行完就退出了。Go 不会自动等待后台 goroutine,哪怕它们正在 http.ListenAndServe 或 conn.Read。
- 错误写法:
go http.ListenAndServe(":8080", nil)后直接结束main,进程秒退 - 正确做法:用
sync.WaitGroup或select {}阻塞主 goroutine,但要注意WaitGroup的Add必须在go前调用 - 更稳妥的替代:对长期运行的服务,直接
select {}(空阻塞)比time.Sleep更可靠,后者只是调试临时手段
for 循环里启多个连接?小心循环变量捕获
批量拨号、并发建立 WebSocket 连接时,如果在 for range 中直接用循环变量启动 goroutine,所有连接最终会尝试连同一个地址——因为闭包捕获的是变量地址,不是值。
- 典型错误:
for _, addr := range addrs { go connect(addr) }→ 全部addr指向最后一次迭代的值 - 修复方式一(推荐):显式传参
go connect(addr),确保每个 goroutine 拥有独立副本 - 修复方式二:在循环内重声明变量
for _, addr := range addrs { addr := addr; go connect(addr) } - 这个坑在线上高频触发,尤其搭配
net.DialTimeout或grpc.Dial时,表现为部分连接成功、部分连错地址且无报错
连接太多导致 goroutine 泛滥?必须加 worker pool 限流
不加控制地为每个连接起一个 goroutine,10 万连接 ≈ 10 万个 goroutine,栈内存轻松吃掉几 GB,调度器压力陡增,甚至触发 runtime: failed to create new OS thread。
- 核心思路:用带缓冲的
chan *net.Conn作任务队列,固定数量 worker goroutine 持续消费 - worker 数量建议设为
runtime.NumCPU() * 2,而非盲目设 100+;过高反而增加调度开销 - channel 缓冲大小要匹配预期突发量,比如峰值每秒 500 连接,则缓冲至少设为 1000,避免 accept goroutine 阻塞在 send 上
- 务必配合
context.WithTimeout控制单次连接处理时长,防止某个卡死的连接拖垮整个 pool
如何安全关闭一批活跃连接?别用 panic 或 os.Exit
服务重启、配置热更、异常熔断时,需要主动关闭所有连接并等待处理完成。硬杀进程或 panic 会导致连接未清理、资源泄漏、数据截断。
- 标准做法:用
context.WithCancel创建可取消上下文,传给每个连接处理 goroutine;收到信号后调用cancel(),各 goroutine 检查ctx.Done()并优雅退出 - 连接对象本身要支持
Close(),并在 defer 中确保调用;读写操作需配合conn.SetReadDeadline避免永久阻塞 - 关闭流程顺序很重要:先停新连接接入(如关闭 listener),再发 cancel,最后
wg.Wait()等待存量处理完毕;顺序颠倒会导致新连接被丢弃或旧连接永远等不到退出信号
真正难的不是启动一堆 goroutine,而是让它们在生命周期各阶段——启动、运行、通信、退出——都保持可观察、可控制、可中断。尤其是连接类资源,一旦失控,问题往往滞后暴露,排查成本远高于预防成本。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











