go程序默认对sigint/sigterm直接退出,不执行defer或清理;必须用signal.ignore屏蔽默认行为,否则notify仅镜像信号而无法拦截终止。

为什么 os/signal.Notify 必须配合 signal.Ignore 或默认行为处理?
Go 程序默认对 SIGINT(Ctrl+C)和 SIGTERM 会直接退出,不执行 defer 或 cleanup。如果只调用 os/signal.Notify 而没屏蔽系统默认行为,信号一来进程就崩了,根本走不到你的清理逻辑。
- 必须在
Notify前调用signal.Ignore(或signal.Stop配合手动恢复),否则信号仍按默认方式终止进程 -
Notify本身只是把信号转发到 channel,不改变系统默认响应;它不“捕获”,只“镜像” - 常见错误:漏掉
signal.Ignore(syscall.SIGINT, syscall.SIGTERM),导致程序看似监听了信号,实则秒退
如何用 context.WithCancel 配合信号 channel 实现统一退出控制?
单靠信号 channel 容易让多个 goroutine 各自判断退出时机,造成竞态或遗漏。用 context 是最自然的解耦方式——所有依赖它的子 goroutine 都能感知取消信号,且 cleanup 可集中写在 defer 里。
- 创建
ctx, cancel := context.WithCancel(context.Background()),在收到信号后调用cancel() - HTTP server、数据库连接池、定时任务等都应接受该
ctx并监听其 Done() channel - 示例:
srv.Shutdown(ctx)比srv.Close()更安全,前者等待活跃请求结束,后者立即断开 - 注意:不要在 signal handler 里阻塞(比如做耗时 I/O),否则会卡住其他信号接收
syscall.SIGQUIT 和 SIGTERM 在生产环境中的实际区别是什么?
本地开发常用 SIGINT(Ctrl+C),但容器编排(如 Kubernetes)默认发的是 SIGTERM;SIGQUIT 则通常用于触发 panic dump,不是优雅退出信号。
- K8s 的
terminationGracePeriodSeconds到期后发SIGKILL,所以你只有SIGTERM这一次机会做 cleanup -
SIGQUIT默认会生成 core dump 并退出,不应被用于 graceful shutdown 流程 - 建议监听
syscall.SIGTERM和os.Interrupt(即SIGINT),覆盖终端和容器两种场景 - Windows 不支持
SIGTERM,要用os.Interrupt+syscall.SIGINT组合兼容
为什么 http.Server.Shutdown 要配超时,且不能依赖 time.Sleep 等待?
Shutdown 是异步的,它返回后不代表所有连接已关闭,只是开始执行关闭流程。盲目 sleep 容易过早退出,或过久等待。
- 必须传入带超时的
context,例如ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) -
Shutdown返回 error 表示超时或失败,此时应记录日志并强制终止资源(如 close DB 连接池) - 不要在 main 函数末尾加
time.Sleep等待,这会让进程无法响应 SIGKILL,违反 K8s 的优雅退出契约 - DB 连接池、消息队列消费者等也要各自实现带 timeout 的 Close 方法,并在 shutdown 流程中串行调用
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











