go中os.signal捕获不到sigint/sigterm,主因是信号未传入进程(如docker默认不转发、systemd未配killmode=process)或未阻塞等待信号;需用channel配合

Go 中 os.Signal 捕获不到 SIGINT 或 SIGTERM?检查信号是否被父进程屏蔽
默认情况下 Go 程序能收到 SIGINT(Ctrl+C)和 SIGTERM,但如果你在容器、systemd 或 shell 脚本里运行,信号可能根本没传进来。比如 docker run 默认不转发 SIGTERM 给主进程;systemd 服务若没配 KillMode=process,会直接杀整个 cgroup,跳过 Go 的信号处理逻辑。
- 用
strace -e trace=rt_sigprocmask,rt_sigaction,kill启动程序,确认信号是否抵达进程 - 容器中加
--init参数(如docker run --init),避免信号被 init 进程吞掉 - systemd 服务文件里显式写
KillSignal=SIGTERM和KillMode=process
signal.Notify 后程序没退出?别忘了阻塞等待信号
signal.Notify 只是注册监听,它不会自动让主 goroutine 等待。常见错误是注册完就直接执行后续逻辑,甚至 main 函数返回,导致程序提前退出,信号根本来不及处理。
- 必须用一个 channel(比如
sigChan := make(chan os.Signal, 1))配合signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - 然后用
阻塞住主 goroutine,等信号到来 - 不要用
time.Sleep或空for {}替代 —— 前者不可靠,后者占满 CPU
优雅退出时资源清理失败?context.WithCancel + sync.WaitGroup 是标配
仅捕获信号不够,HTTP server 关闭、数据库连接释放、goroutine 退出都需要时间。直接退出会导致连接中断、数据丢失或 panic。
- 启动前创建
ctx, cancel := context.WithCancel(context.Background()),把ctx传给所有需要响应退出的组件 - 每个长期运行的 goroutine 开头加
select { case - 用
sync.WaitGroup记录活跃 worker,cancel()后调wg.Wait()等它们结束 - HTTP server 关闭要带超时:
srv.Shutdown(ctx),而不是srv.Close()
为什么 SIGHUP 在某些环境收不到?Go 默认不监听它
Go 的 os/signal 包默认只关注 SIGINT 和 SIGTERM,SIGHUP(比如终端断开、nohup 启动)需显式添加。而且 Linux 下,如果进程不是 session leader,SIGHUP 可能根本不会发给它。
立即学习“go语言免费学习笔记(深入)”;
- 要支持
SIGHUP,必须在signal.Notify里加上:syscall.SIGHUP - 守护进程需调用
syscall.Setpgid(0, 0)或使用setsid()成为 session leader,否则收不到SIGHUP - 注意:Windows 不支持
SIGHUP,跨平台代码要加// +build !windows构建约束
信号处理最易被忽略的点是「信号到达」和「程序响应」之间存在非原子间隙:从收到信号到执行 cleanup,中间任何 panic、死锁或未覆盖的 goroutine 都会让退出卡住。别假设所有资源都支持 context 取消 —— 有些底层库(比如老版本 pgx、某些 Cgo 封装)需要手动关连接、调 Close() 或发自定义停止指令。










