os.exit() 不能触发 defer 清理,因为它会立即终止进程,不执行函数返回路径,导致所有 goroutine(包括 main 和 worker)中的 defer 语句全被跳过,临时文件无法清理。

为什么 os.Exit() 不能触发 defer 清理
因为 os.Exit() 是立即终止进程的系统调用,它不走函数返回路径,所有 defer(包括 main 函数里的、goroutine 里的)全被跳过。哪怕你在 worker goroutine 里写了 defer os.Remove("tmp.txt"),只要主 goroutine 调了 os.Exit(),那个文件就永远留着。
- 常见错误现象:
Ctrl+C后程序退出,但临时目录里一堆temp_*.log残留 - 根本原因:goroutine 被强制销毁,其栈帧不会“返回”,
defer失效 - 别指望靠
runtime.SetFinalizer或os.CleanUp补救——Go 没这机制
signal.Notify 必须配带缓冲 channel
signal.Notify 如果往无缓冲 channel 发送信号,而接收端还没开始 select 或 ,信号就丢了。生产环境一按 <code>Ctrl+C 没反应,大概率是这儿卡住了。
- 正确写法:
sigCh := make(chan os.Signal, 1)—— 缓冲区大小至少为 1 - 注册信号时至少包含
syscall.SIGINT和syscall.SIGTERM,缺一不可 - 别用
os.Interrupt替代syscall.SIGINT:前者只是后者的别名,但混用容易在跨平台时出错(比如 Windows 的CTRL_BREAK_EVENT)
goroutine 退出必须用 context 或 close channel 驱动
Go 没有“杀 goroutine”API,所有退出都得靠它自己检查退出信号后 return。用 context.Context 是最稳妥的选择,尤其当 goroutine 之间有父子关系或需要超时控制时。
- worker 内部必须用
select监听ctx.Done(),不能只轮询ctx.Err() != nil(会 CPU 空转) - 启动 goroutine 时传入子 context:
ctx, cancel := context.WithCancel(parentCtx),清理前调cancel() - 如果只是平级协作,用
stopCh := make(chan struct{})更轻量;主 goroutine 执行close(stopCh),worker 收到case 就该 <code>return - 切记:已关闭的 channel 上再
send会 panic,所有写操作必须在case分支内提前return
WaitGroup + 显式 cleanup 是唯一可靠组合
等所有 goroutine 自己退出还不够——你还得确保它们真干完了清理。靠 defer 不行,靠 sync.Once 也不行,必须显式协调。
- 每个长期运行的 goroutine 启动前调
wg.Add(1),退出前调wg.Done() - 信号到来后,先发退出通知(
cancel()或close(stopCh)),再wg.Wait() - 清理逻辑(如
os.RemoveAll(tempDir))必须放在wg.Wait()之后、main函数 return 之前 - 加超时保护更安全:
select { case
真正难的不是写完这四步,而是把资源创建和清理配对放到同一作用域里——比如临时文件在 workerWithCleanup() 函数里生成,就得在同一个函数里注册进 cleanup list,而不是散落在各处靠“记得删”来维护。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











