直接用 go func() 启动订单处理会导致并发不安全,因 goroutine 泛滥和缺乏协调引发连接池耗尽、库存错乱等问题;应改用带缓冲 channel 作任务队列,配合固定数量 worker 并监听 ctx.Done() 实现可控并发与优雅退出。
go func() 启动订单处理会出问题并发不等于安全。很多开发者一上来就对每个订单起一个 go func(),结果发现数据库连接被打爆、库存扣减错乱、日志混成一团。根本原因在于:没有节制的 goroutine 泛滥 + 缺乏任务协调机制。比如 1000 个订单进来,瞬间 spawn 1000 个 goroutine,而数据库连接池可能只有 20 个,panic: dial tcp: lookup xxx: no such host 或 too many connections 就是典型表现。
真正需要的是可控的并发度 + 明确的任务生命周期管理 —— 这正是 channel 的核心价值。
chan *Order 做任务队列 + 固定 worker 数量把订单当消息塞进 channel,由一组固定数量的 worker 从 channel 中取任务执行。这样既限流又解耦,还能自然支持优雅退出。
workerCount 通常设为 CPU 核心数 × 1.5 ~ 2(I/O 密集型可更高),避免过度抢占调度器make(chan *Order, 100)),否则生产者可能被阻塞,导致上游 HTTP handler 卡住ctx.Done(),收到取消信号时完成当前任务后退出func startWorkers(ctx context.Context, tasks chan *Order, workerCount int) { var wg sync.WaitGroup for i := 0; i < workerCount; i++ { wg.Add(1) go func() { defer wg.Done() for { select { case order, ok := <-tasks: if !ok { return } processOrder(order) // 实际业务逻辑 case <-ctx.Done(): return } } }() } go func() { <-ctx.Done() close(tasks) wg.Wait() }() }
select 配合 default 实现非阻塞投递与背压控制上游(比如 HTTP handler)往 tasks channel 发送订单时,不能无条件 tasks ,否则在 channel 满时会阻塞整个请求处理流程。需要用 select + default 做快速失败或降级。
立即学习“go语言免费学习笔记(深入)”;
429 Too Many Requests,而不是让请求 hang 住sync.Map)暂存,再异步批量刷入 channel,但会增加复杂度default 分支里加 time.Sleep 循环重试 —— 这等于自己实现低效轮询,且容易拖垮 handlerfunc handleOrder(w http.ResponseWriter, r *http.Request) {
order := parseOrder(r)
select {
case tasks <- order:
json.NewEncoder(w).Encode(map[string]string{"status": "accepted"})
default:
http.Error(w, "system busy", http.StatusTooManyRequests)
}
}直接 close(tasks) 是危险的 —— 如果有 worker 正在读取,会收到零值;如果多个 goroutine 同时 close 同一个 channel,会 panic:panic: close of closed channel。必须确保仅由单一 goroutine 关闭,且关闭前所有生产者已停止写入。
context.WithCancel 控制生命周期,关闭信号由上层统一触发for order := range tasks 自动退出,比手动 select 更简洁可靠sync.WaitGroup 等待所有 worker 退出后再返回,否则进程可能提前结束,导致部分订单丢失最易忽略的一点:如果你在 worker 里调用了外部服务(如支付回调、消息推送),这些操作本身可能超时或失败,必须单独设置超时和重试策略 —— channel 只管调度,不负责业务容错。