无缓冲channel适用于强同步场景,如主协程等待子协程完成并发送信号;不可用作任务队列,否则主协程会在首个发送操作阻塞。

微服务里并发控制不是“要不要做”,而是“怎么选对 channel 模式”——用错 chan 类型或结构,轻则吞吐掉一半,重则 goroutine 泄漏、内存暴涨。
无缓冲 channel 用于强同步场景,但别在高并发任务分发中硬套
无缓冲 chan 是同步点:发送方必须等到接收方就绪才返回。适合「主协程等子协程完成」这类明确一对一等待的逻辑,比如服务启动时初始化 N 个组件,每个组件完成后发一个 struct{} 信号。
- 错误用法:用无缓冲
chan当任务队列,比如jobs := make(chan Task)然后直接往里塞 1000 个任务 → 主协程会卡死在第一个jobs - 正确做法:只在真正需要阻塞等待结果的地方用,例如健康检查完成通知、配置加载完毕信号
- 注意闭包捕获变量:循环中启动 goroutine 时,若直接用循环变量
i,所有 goroutine 可能读到同一个终值;必须用临时变量拷贝,如ti := i; go func() { ... }()
有缓冲 channel 是工作池的标配,容量必须等于最大并发数
工作池(worker pool)是微服务中最常见的并发控制模式,靠有缓冲 chan 解耦任务生产与消费。缓冲大小不是随便设的 —— 它直接决定最大并发数,也影响内存占用和背压行为。
- 缓冲太小(如
make(chan Task, 1)):任务堆积快,goroutine 频繁阻塞,调度开销上升 - 缓冲太大(如
make(chan Task, 10000)):内存占用不可控,且失去限流意义,下游处理不过来时任务全堆在 channel 里 - 推荐设置:缓冲大小 = worker 数量 × 预估单任务平均耗时 / 平均到达间隔;保守起见,先设为 worker 数量的 2–3 倍,再根据监控调优
- 别忘了
close(ch):向任务 channel 发完所有任务后必须关闭,否则for range ch永远不会退出
select + timeout 是避免 goroutine 卡死的底线操作
微服务调用外部依赖(DB、HTTP、RPC)时,channel 等待必须带超时,否则一个慢请求就能拖垮整个实例。纯 是危险的,<code>select 才是安全入口。
立即学习“go语言免费学习笔记(深入)”;
- 常见错误:写
result := 等结果,但上游服务挂了 → 这个 goroutine 永久阻塞,泄漏 - 正确写法:总是配
time.After或context.WithTimeout,例如:
select {
case result := <-resultCh:
handle(result)
case <-time.After(5 * time.Second):
log.Warn("timeout waiting for result")
return ErrTimeout
}- 注意:不要在 select 里重复使用同一个
time.After变量,它是一次性的;每次超时判断都该新建 - 更推荐用
context.Context,尤其在 HTTP handler 中,天然携带 cancel 和 deadline
别混用 sync.WaitGroup 和 channel 做同一类等待
WaitGroup 和 channel 都能等 goroutine 结束,但语义不同:WaitGroup 是「计数型等待」,channel 是「信号型等待」。混用容易导致逻辑混乱或资源没释放。
- WaitGroup 适合:启动一批 goroutine 做独立工作,不返回数据,只关心是否全部结束(如日志 flush、metrics 上报)
- channel 适合:需要收集结果、或依赖顺序、或要配合超时/取消(如批量调用下游并聚合响应)
- 典型陷阱:用 WaitGroup 等 worker,又同时从
resultschannel 收数据,但没控制好关闭时机 →range results卡住,因为 channel 没 close - 关键原则:谁 close channel,谁负责;通常由发送方(worker 启动者)在发完所有任务后 close
jobs,由接收方在收完所有结果后 closeresults
真正难的不是写出能跑的并发代码,而是让 channel 的阻塞/缓冲/关闭行为和业务生命周期严丝合缝 —— 微服务里一次 goroutine 泄漏,可能几小时后才在 pprof 里看到内存曲线异常爬升。


















