用无缓冲channel做任务队列易卡死,因未分离投递与执行节奏;需设缓冲(如make(chan task, 100))并配协程池消费,否则生产快、消费慢即阻塞。

Go 用 channel 做简单任务队列,为什么容易卡死?
直接用 chan 实现任务分发,不加缓冲或控制并发,很快就会阻塞在 send 或 recv。根本原因是:没有分离「投递」和「执行」节奏,生产者一快、消费者一慢,整个流程就停住。
- 别用无缓冲
chan接收外部请求——taskCh := make(chan Task)是典型陷阱 - 至少设缓冲:
taskCh := make(chan Task, 100),但缓冲不是万能解,只是买点时间 - 必须配工作协程池,比如
for i := 0; i ,否则没人消费 - 记得在
worker里用for task := range taskCh,别只读一次就退出
要不要上 redis 或 asynq?看这三点
本地 channel 只适合单进程、短生命周期、不关心失败重试的场景。一旦要持久化、多实例、延时任务或失败告警,就得换方案。
- 如果任务丢了不能接受(比如支付回调),
channel不行,必须用redis+asynq或machinery -
asynq的Client和Server分离清晰,但默认不开启redis密码认证,上线前得配asynq.RedisConnOpt{Password: "xxx"} - 用
asynq时,任务结构体字段必须是导出的(首字母大写),否则序列化后丢失:type SendEmailTask struct { To string `json:"to"` },不是to string
time.AfterFunc 能当延时队列用吗?
能,但仅限秒级、低频、不关心准确性的临时需求。它本质是单次定时器,没队列语义,也不支持取消已排队但未触发的任务。
-
time.AfterFunc(5 * time.Second, func() { doWork() })—— 这个调用完就返回,无法获取句柄 - 想取消?得自己包一层:
timer := time.NewTimer(5 * time.Second); go func() { ,然后用 <code>timer.Stop() - 高并发下大量
time.AfterFunc会撑爆runtime.timer内存,压测时注意GODEBUG=gctrace=1观察
用 sync.WaitGroup 等所有任务结束,为什么有时候不生效?
常见于协程启动后立即 wg.Wait(),但 wg.Add() 没在协程外提前调好,或者 wg.Done() 被 panic 绕过。
- 必须先
wg.Add(n),再起n个 goroutine;反过来就会漏计数 - 别在匿名函数里直接
defer wg.Done()后接可能 panic 的代码,panic 会跳过 defer;改用defer func(){ wg.Done() }()包一层 - 如果任务本身要等子任务(比如 HTTP 请求+DB 写入),
WaitGroup只管最外层,里面得自己再套一层
实际跑起来才发现,最难的不是选哪个库,而是决定「任务失败时该重试几次」「要不要记录执行日志到单独表」「下游服务不可用时任务该进死信还是丢弃」——这些逻辑没写进任何 SDK 文档里,全靠你对着业务流一笔笔对。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











