该用 goroutine 而非系统线程时:需高并发处理大量 I/O 等待任务(如 HTTP 请求、数据库查询)或成千上万活跃连接;避免用于 CPU 密集型循环或需串行执行的共享状态更新。

什么时候该用 goroutine 而不是系统线程
Go 的并发模型核心是轻量级的 goroutine,它不是对 OS 线程的简单封装,而是在用户态由 Go 运行时调度的协程。当你需要同时处理成百上千甚至上万的活跃连接(比如 HTTP 服务、消息代理、实时推送),或执行大量 I/O 等待任务(如数据库查询、RPC 调用、文件读写)时,goroutine 比直接创建系统线程更合适。
常见误用场景包括:在 CPU 密集型循环里无节制启动 goroutine(比如暴力遍历计算),这不会提升性能,反而因调度开销和内存占用拖慢整体速度;或者把本该串行的逻辑(如共享状态更新)强行并发,引发竞态。
- 适合:
http.Server每个请求启一个goroutine、长连接心跳管理、批量调用第三方 API 并聚合结果 - 不适合:
for i := 0; i (应改用 worker pool + channel 控制并发数) - 关键判断点:任务是否常被 I/O 或 channel 操作阻塞?阻塞时能否让出执行权给其他
goroutine?
channel 传值还是传指针?为什么 select 容易卡死
向 channel 发送数据会拷贝值。如果结构体较大(比如含 []byte 或嵌套 map),传值开销明显;但若只传指针,需确保接收方不意外修改原始数据,或在 goroutine 生命周期外访问已释放内存。
select 卡死通常是因为所有 case 对应的 channel 都不可读/不可写,且没写 default 分支——此时当前 goroutine 会永久阻塞。这不是 bug,而是设计行为。
立即学习“go语言免费学习笔记(深入)”;
- 小结构体(
int、string、不超过 3 个字段的 struct)传值更安全清晰 - 大对象或需修改原值时传指针,但要在文档或注释中标明所有权归属
-
select必须加default:(非阻塞尝试)或case (超时控制),否则极易成为隐蔽死锁源
如何避免 context.WithCancel 泄漏导致 goroutine 积压
用 context.WithCancel 创建子 context 后,若父 context 已结束但子 context 未被显式取消,关联的 goroutine 可能持续运行并持有资源(如数据库连接、文件句柄)。典型例子是 HTTP handler 中启了后台 goroutine 但没监听 req.Context().Done()。
- 每个启动的
goroutine都应监听某个context.Context的Done()通道,并在收到信号后退出 - 避免把
context.Background()直接传给长期运行的goroutine,除非你明确它必须永生 - 用
pprof/goroutines定期检查运行中 goroutine 数量突增,配合runtime.Stack定位泄漏源头
sync.Pool 真的能省内存吗?什么情况下反而有害
sync.Pool 适用于高频创建销毁、大小相对固定、且初始化开销较大的对象(如 bytes.Buffer、自定义 parser 结构体)。但它不是万能缓存:Pool 中的对象可能被 GC 清理,且无淘汰策略,滥用会导致内存驻留时间过长或误复用脏数据。
- 必须实现
New函数,且确保返回的对象是“干净”的(例如bytes.Buffer需要.Reset()) - 不要把含闭包、外部引用或非线程安全字段的对象放进 Pool
- 在低频或生命周期不规律的场景下(如每秒只创建几次的对象),用 Pool 反而增加调度负担,不如直接 new
高并发真正的难点不在启动多少 goroutine,而在控制它们何时停、数据怎么交、错误怎么传、资源怎么收。这些细节不写进日志,也不报 panic,但会在 QPS 上升到 5000 之后悄悄吃掉你的延迟毛刺和内存稳定性。











