背景

关闭任务队列之前如果仍有提交者写入,会发生 send on closed channel。关闭一次和关闭安全不是同一件事。

核心判断

  1. 先通过统一入口停止提交,再等待所有提交者退出。
  2. 唯一协调者关闭 jobs,worker 遍历队列排空后退出。
  3. 最后等待 worker;任务本身仍应遵守超时与取消。

最小示例

下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。

// 前提:统一入口已拒绝新提交,所有可能发送 jobs 的协程都已退出。
// 仅由协调者调用,不从 worker 内调用。
func (p *Pool) drain() {
    p.once.Do(func() {
        close(p.jobs)
        p.wg.Wait()
    })
}

func (p *Pool) worker(ctx context.Context) {
    defer p.wg.Done()
    for job := range p.jobs {
        job.Run(ctx)
    }
}

边界与误区

sync.Once 只能防止重复 close,不能防止并发 send。示例的 drain 要在提交者已经退出后由协调者调用,不得从 worker 自身调用以免等待自己。

验证方式

用竞态检测测试提交与停止交错,再验证空队列、阻塞任务和重复停止。明确关闭是排空任务还是丢弃任务,并对未完成结果提供可追踪状态。

小结

关闭时先禁止新提交,再关闭队列并等待 worker,任务自身要响应 context。