main 返回会立即终止所有 goroutine,defer 不执行、资源不释放;应使用 sync.waitgroup 精确等待或 select{} 永久挂起主 goroutine,并确保 wg.wait() 后再关闭资源。

main 返回就 kill 全部 goroutine,这是确定行为
Go 没有“后台线程”概念,main 函数一返回,整个进程立刻被操作系统回收。所有正在运行的 goroutine(包括 http.ListenAndServe、文件写入、数据库查询)都会被强制终止——defer 不执行、close() 不触发、缓冲未刷盘、日志截断。这不是 bug,是 Go 运行时的明确设计。
用 sync.WaitGroup 等确定数量的 goroutine
当你清楚要等几个 goroutine(比如启动 3 个 worker、1 个监控器),sync.WaitGroup 是最直接、无歧义的选择:
-
wg.Add(n)必须在go语句之前调用,不能放在 goroutine 内部 - 每个 goroutine 入口加
defer wg.Done(),确保 panic 时也能计数归零 -
wg.Wait()放在main最后一行,它会阻塞直到所有Done()被调用 - 避免在 goroutine 外提前关闭资源(如
db.Close()),必须等wg.Wait()之后再关
示例:
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() { defer wg.Done(); workA() }()
go func() { defer wg.Done(); workB() }()
wg.Wait() // 阻塞至此
fmt.Println("all done")
}
用 select{} 永久挂起主 goroutine
适用于长期驻留的服务型程序(HTTP server、消息消费者),不需要精确计数,只希望主 goroutine 不退出:
-
select{}是零 CPU 占用的永久阻塞,Go 官方文档和标准库都这么用 - 不要用
for {}或time.Sleep(100 * time.Hour),前者吃满 CPU,后者掩盖逻辑缺陷 - 若程序中只有
select{}且无其他 goroutine,会 panic:fatal error: all goroutines are asleep - deadlock - 生产环境建议搭配退出信号通道(如
os.Signal)做优雅关闭,而不是真“永久”
简单常驻写法:
func main() {
go http.ListenAndServe(":8080", nil)
go consumeMessages()
select {} // 主 goroutine 在此挂起
}
goroutine 启动后不执行?先查 main 是否已返回
这是绝大多数“goroutine 消失”问题的第一怀疑点。验证方法极简单:
- 在疑似没跑的 goroutine 里加一行
fmt.Println("goroutine started") - 在
main最后加fmt.Println("main exiting") - 如果只看到 “main exiting”,说明 goroutine 根本没来得及调度或刚启动就被杀掉了
- 别用
time.Sleep临时补救:快慢不可控,且掩盖了同步缺失的本质问题
真正需要的是显式同步机制,不是延时凑数。
最容易被忽略的是闭包变量捕获和资源关闭时机——wg.Wait() 前关 DB、在循环里直接引用 i 而不传参,这些都会让等待失效或导致数据错乱。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











