
Go 程序在 main 函数返回时立即终止,无论其他 goroutine 是否仍在运行;本教程通过分析典型单通道示例,揭示因缺少同步导致的“只打印一个数字”的根本原因,并提供基于 channel 的优雅解决方案。
go 程序在 `main` 函数返回时立即终止,无论其他 goroutine 是否仍在运行;本教程通过分析典型单通道示例,揭示因缺少同步导致的“只打印一个数字”的根本原因,并提供基于 channel 的优雅解决方案。
在 Go 并发编程中,一个常见误区是认为只要向 channel 发送数据、另一端有 goroutine 接收,程序就会自然按预期顺序执行完毕。但事实并非如此——Go 运行时不会等待未完成的 goroutine。一旦 main 函数执行结束,整个程序立即退出,所有正在运行的 goroutine(包括尚未完成接收的)都会被强制终止。
回到你的代码:
func main() {
c := make(chan int)
go func() {
for i := 0; i <p>这里的关键问题在于: </p>
-
go func()启动后立即返回,main继续执行; -
time.Sleep(0)不产生有效延迟(Go 规范中明确说明其行为等价于无休眠),因此sendNumbers(c)几乎立刻被调用; -
sendNumbers向 channel 发送两个值0和1,但此时接收 goroutine 可能仅来得及读取第一个(0),随后main函数已执行完毕并退出,导致第二个值1被丢弃,goroutine 被强制终止。
⚠️ 注意:time.Sleep(1 * time.Second) “生效”,只是因为人为延缓了 main 退出时机,让接收 goroutine 有机会读完两个值——但这不是可靠的同步方式,它依赖不确定的调度时序,且违背 Go 的并发设计哲学。
✅ 正确做法:用 channel 实现确定性同步
Go 推荐使用 channel 本身作为同步原语。一种简洁、惯用的方式是添加一个“完成通知 channel”,让接收端在处理完所有数据后显式告知 main:
package main
import "fmt"
func sendNumbers(c chan int) {
for i := 0; i <p>✅ 输出始终为:</p><pre class="brush:php;toolbar:false;">0
1该方案的优势在于:
- 无竞态、无超时依赖:完全基于 channel 的阻塞语义,行为可预测;
-
资源安全:
close(c)配合range确保不会漏读或 panic; - 符合 Go 惯例:用 channel 通信代替共享内存和显式 sleep,体现 “Don’t communicate by sharing memory; share memory by communicating” 原则。
小结
- ❌
time.Sleep不是同步机制,仅用于调试或模拟延迟; - ✅ 所有需等待 goroutine 完成的场景,必须引入显式同步(
sync.WaitGroup或 channel); - ? 对于 producer-consumer 模式,
close + range是最自然、最安全的 channel 结束信号; - ? 若需多次复用 channel,应避免
close,改用带缓冲的 done channel 或sync.WaitGroup。
掌握这一模式,是写出健壮 Go 并发程序的第一步。










