
在 Go 并发编程中,done 通道用于可靠地等待工作 goroutine 执行完毕,避免主 goroutine 提前退出导致子任务被意外截断。
在 go 并发编程中,`done` 通道用于可靠地等待工作 goroutine 执行完毕,避免主 goroutine 提前退出导致子任务被意外截断。
Go 的并发模型依赖于 goroutine 和 channel 协作,但goroutine 的执行是异步且不可预测的——主 goroutine 不会自动等待其他 goroutine 结束。看似“逻辑上已完成”的操作(如关闭 jobs 通道、打印 "sent all jobs"),并不意味着工作 goroutine 已处理完所有数据,更不保证其内部逻辑(如日志输出、资源清理)已执行完毕。
例如,在 Go by Example 的 closing-channels 示例 中,done 通道的作用并非“传递业务状态”,而是作为同步信号:主 goroutine 通过 阻塞等待,确保工作 goroutine 真正退出后才继续(或结束程序)。
若移除 done 通道(如 Playground 示例),程序行为将变得非确定性:
// ❌ 危险:无同步,主 goroutine 可能立即退出
for j := 1; j <= 3; j++ {
jobs <- j
fmt.Println("sent job", j)
}
close(jobs)
fmt.Println("sent all jobs")
// 主函数在此结束 → 整个程序退出!
// 此时 worker goroutine 可能尚未执行完甚至未开始运行添加 time.Sleep(500) 到 worker 中(见修正版 Playground r5hfTZ79tA)可直观暴露问题:输出中完全缺失 "received job X" 和 "received all jobs" —— 因为主 goroutine 在 worker 还未调度或仅处理部分任务时就已终止。
✅ 正确做法是使用 done(或更现代的 sync.WaitGroup/context)实现显式同步:
func main() {
jobs := make(chan int, 5)
done := make(chan bool) // 同步信号通道
go func() {
for j := range jobs { // 更简洁:range 自动检测关闭
time.Sleep(500)
fmt.Println("received job", j)
}
fmt.Println("received all jobs")
done <- true // 通知完成
}()
for j := 1; j <= 3; j++ {
jobs <- j
fmt.Println("sent job", j)
}
close(jobs)
fmt.Println("sent all jobs")
<-done // ✅ 阻塞等待 worker 完全退出
}关键总结:
- 关闭通道(
close(ch))仅表示“不再发送”,不等于“已被完全接收”;range ch或j, ok := 能检测关闭状态,但无法通知<strong>调用方何时处理完毕</strong>;done通道(或sync.WaitGroup.Done())是跨 goroutine 传达“我已彻底完成”的标准机制;- 忽略同步将导致竞态、输出丢失、资源泄漏等隐蔽错误,尤其在高负载或不同运行环境(如 CI/容器)下更易复现。
因此,done 不是冗余代码,而是 Go 并发安全与可预测性的基石之一。

















