
Go 程序在 main 函数返回时立即终止,不等待其他 goroutine 完成;即使通道收发操作天然阻塞,若缺乏同步机制,接收 goroutine 可能来不及处理全部数据便随主程序退出而被强制终止。
go 程序在 `main` 函数返回时立即终止,不等待其他 goroutine 完成;即使通道收发操作天然阻塞,若缺乏同步机制,接收 goroutine 可能来不及处理全部数据便随主程序退出而被强制终止。
在您提供的代码中,问题本质并非通道“不阻塞”,而是程序生命周期管理缺失。让我们逐步分析:
func main() {
c := make(chan int)
go func() {
for i := 0; i
-
go func()启动一个接收 goroutine,它首次从c接收时会阻塞(因 channel 为空),直到sendNumbers(c)开始发送。 -
sendNumbers(c)在main中同步执行:先发0→ 接收 goroutine 解阻塞、打印0、继续循环 → 再次阻塞等待; - 此时
sendNumbers(c)发送1→ 接收 goroutine 解阻塞、打印1、循环结束、goroutine 正常退出。
看似逻辑完整——但关键在于:main 函数在 sendNumbers(c) 返回后立即结束,整个程序随之退出。而 Go 运行时不会保证已启动的 goroutine 执行完成。即使接收 goroutine 正在打印 1 或刚完成,只要 main 返回,进程即终止,可能导致输出截断(如仅看到 0)或完全丢失 1。
time.Sleep(1 * time.Second) “生效”,只是巧合地让 main 多存活了一秒,给了接收 goroutine 足够时间完成两次接收和打印——但这属于竞态依赖时间延迟的脆弱方案,绝不可用于生产环境。
✅ 正确解法:使用同步机制确保 main 等待接收完成。推荐两种 Go 风格方案:
方案一:使用 sync.WaitGroup(显式计数)
package main
import (
"fmt"
"sync"
)
func sendNumbers(c chan int) {
for i := 0; i <p><strong>方案二:纯通道同步(更符合 Go 并发哲学)</strong></p><pre class="brush:php;toolbar:false;">package main
import "fmt"
func sendNumbers(c chan int, done chan struct{}) {
for i := 0; i <p>⚠️ 注意事项:</p>
- 永远不要用
time.Sleep做 goroutine 同步——它不可靠、难维护、掩盖了并发设计缺陷; - 向已关闭的 channel 发送数据会 panic,接收则得到零值+
false;务必明确关闭责任方(通常是发送方); -
range配合close()是处理“有限序列接收”的惯用模式,比手动计数更安全; -
main函数是程序唯一入口和出口,其返回即全局终止——这是 Go 并发模型的基础约束,必须主动应对。
掌握这一机制,是写出健壮 Go 并发程序的第一课:阻塞是通道的特性,而协调是程序员的责任。










