
go程序主函数退出后会立即终止所有goroutine,若未同步等待,异步启动的goroutine可能根本来不及执行——这是初学者最常遇到的“无输出”问题。
go程序主函数退出后会立即终止所有goroutine,若未同步等待,异步启动的goroutine可能根本来不及执行——这是初学者最常遇到的“无输出”问题。
在Go中,go关键字启动的goroutine是并发而非并行的轻量级协程,但其生命周期完全依赖于主 goroutine(即 main 函数)的运行状态。一旦 main 函数执行完毕并返回,整个程序立即退出,所有尚未完成的 goroutine 会被强制终止,不会获得调度执行的机会。
以原始代码为例:
package main
import "fmt"
func main() {
for i := 0; i < 20; i++ {
go func(j int) {
fmt.Println(j + 100)
}(i)
}
}该程序编译通过、运行无报错,但通常不输出任何内容(极少数情况下可能打印零星几行,属竞态偶然行为)。原因在于:for 循环快速启动20个 goroutine 后,main 函数立即结束,进程退出,底层调度器甚至来不及将这些 goroutine 置入运行队列。
✅ 正确做法是使用同步机制显式等待所有 goroutine 完成。推荐方案是 sync.WaitGroup:
立即学习“go语言免费学习笔记(深入)”;
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1) // 每启动一个goroutine,计数器+1
go func(j int) {
defer wg.Done() // goroutine结束时自动-1
fmt.Println(j + 100)
}(i)
}
fmt.Println("Waiting...")
wg.Wait() // 阻塞直到计数器归零
fmt.Println("Done.")
}⚠️ 注意事项:
-
wg.Add(1)必须在go语句之前调用,避免因调度延迟导致Add和Done顺序错乱; -
defer wg.Done()是惯用写法,确保即使函数中途 panic 也能正确计数; - 切勿在循环中直接捕获循环变量
i(如go func(){ fmt.Println(i) }()),否则所有 goroutine 可能打印相同值(闭包陷阱)——本例已通过参数j int正确传值,规避了该问题; - 在生产环境中,还可结合
context.Context实现带超时或取消的等待,增强健壮性。
总结:Go 的 goroutine 不是“后台守护线程”,而是与主流程强耦合的协作式任务。理解“主 goroutine 结束 = 程序终止”这一核心原则,是掌握 Go 并发编程的第一课。


















