用 channel 可实现任务间显式依赖链:a 完成后发送信号,b 通过接收该信号阻塞等待,从而确保“a 必须完成,b 才能开始”;关键在于用阻塞传递控制权而非传输数据。

用 channel 实现任务间的显式依赖链
Go 没有内置的“任务调度器”,但用 chan struct{} 或 chan error 就能清晰表达“A 必须完成,B 才能开始”这种硬依赖。关键不是传数据,而是用阻塞传递控制权。
- 发送方(A)在逻辑末尾执行
done 或 <code>close(done),表示“我好了” - 接收方(B)在开头写
,会卡住直到 A 发信号——这比轮询或 sleep 更精确、零资源浪费 - 多个依赖时,别复用一个
done通道:A→B→C 应该用两个通道ab, bc,否则 B 和 C 会竞争同一个信号 - 注意关闭时机:
close()只能由发送方调用一次;若 A 可能 panic,建议用带error的通道,让 B 通过err, ok := 判断是否失败
多个并行任务完成后才触发下游,用 WaitGroup 还是 channel?
当你要等 N 个 goroutine 全部返回结果(比如批量 HTTP 请求),sync.WaitGroup 是最轻量的选择;但若下游还需消费结果值,就得搭配 channel —— 单靠 WaitGroup 不传数据。
- 只关心“是否结束”:用
WaitGroup。先wg.Add(n),每个 goroutine 结束前wg.Done(),主协程wg.Wait() - 既要等结束,又要收结果:用带缓冲的
results := make(chan Result, n),每个 goroutine 处理完results ,主协程 <code>for i := 0; i - 别在循环里反复
go func() { ... wg.Done() }()却不传参——变量捕获错误会导致部分 goroutine 操作了错误的wg实例 - 如果任务可能提前失败,
WaitGroup无法中断其他 goroutine;此时必须用context.Context配合select做取消传播
多个前置任务中任一完成即可启动后续,用 select 监听多 channel
典型场景如“找最快响应的 API”或“降级兜底”——不需要等全部,只要一个就走。这时不能用单个共享 channel,得为每个前置任务准备独立 channel。
- 给每个任务配一个
resCh := make(chan Result, 1),启动 goroutine 后立即发结果到对应 channel - 下游用
select同时监听所有resCh:select { case r1 := - 收到第一个后,要防止其他 goroutine 继续写入已无消费者的 channel 导致 panic——可在启动时加
defer close(resCh),或用default分支做非阻塞发送 - 不要用
time.After在循环里反复创建;超时控制应统一用ctx, cancel := context.WithTimeout(parentCtx, time.Second),再传给各 goroutine
顺序消费并行结果:为每个任务建独立 channel
当你需要“按固定顺序处理并行产出的结果”(比如解析 header/body/footer 并拼成完整文档),千万别让所有 goroutine 往同一个 channel 写——调度不确定性会让顺序错乱。
- 正确做法:为每个子任务建专属 channel,如
headerCh, bodyCh, footerCh - 主协程严格按顺序读:
h := ,天然保证输出顺序 - 每个 channel 只由一个 goroutine 写,避免竞态;写完可
close(ch),让主协程用range安全遍历(但注意:只适用于单次写入场景) - 如果某个任务可能失败或超时,需配合
select+ctx.Done(),否则主协程会在那个 channel 上永久阻塞
真正容易被忽略的是 channel 的所有权和生命周期管理:谁创建、谁关闭、谁读、谁写,必须在设计阶段就定死。混用 close() 和 send、多个 goroutine 共用一个发送端、或在未确认接收方存活时就发信号——这些都会导致 panic 或死锁,且往往在线上流量高峰才暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











