os/signal是Go跨平台信号处理唯一可靠入口,通过标准库自动适配底层系统调用;需混用os.Interrupt(跨平台Ctrl+C)和syscall.SIGTERM(Linux/macOS终止),Windows下仅os.Interrupt有效,缓冲通道大小设1最安全,子进程需显式转发信号。

os/signal 是 Go 实现跨平台信号处理的唯一可靠入口,它本身不跨平台——而是通过标准库自动适配底层系统调用(syscall 或 x/sys),你不需要手动判断 GOOS 或调用平台专属 API。
为什么不能直接用 syscall.SIGTERM 而要用 os.Interrupt 和 syscall.SIGTERM 混用
因为 os.Interrupt 是 Go 提供的跨平台抽象:在 Unix 系统上等价于 syscall.SIGINT,在 Windows 上映射为 Ctrl+C 事件(非真实信号,但行为一致)。而 syscall.SIGTERM 在 Windows 上根本不存在——编译会通过,但运行时注册无效,signal.Notify 不会收到任何东西。
- 正确做法:始终用
os.Interrupt处理用户中断,用syscall.SIGTERM处理外部终止请求(如kill -15),并在 Linux/macOS 上同时注册;Windows 下只需注册os.Interrupt即可 - 错误写法:
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT)—— 在 Windows 上 SIGTERM 会被静默忽略,且无法检测是否生效 - 推荐写法:
signal.Notify(ch, os.Interrupt, syscall.SIGTERM),Go 标准库内部会按平台过滤掉不支持的信号
signal.Notify 的缓冲通道大小设为 1 还是更大?
设为 1 是最常见也最安全的选择,但前提是你的信号处理逻辑必须快进快出。如果清理耗时(比如要关闭数据库连接、等待 HTTP server graceful shutdown),多个信号可能被丢弃——因为通道满了,新信号写不进,signal.Notify 会直接丢弃。
- 设为 1:适合简单退出,或配合
context.WithTimeout控制清理时间 - 设为 2:能兜住「第一次信号触发清理,第二次信号强制退出」的场景(例如:SIGTERM 启动优雅关机,再收一次 SIGTERM 就
os.Exit(1)) - 绝对不要设为 0(无缓冲):一旦处理 goroutine 阻塞,后续所有信号都会丢失,且程序可能死锁
如何让子进程也响应父进程收到的信号
Go 默认不会自动转发信号给子进程。如果你用 exec.Command 启动了外部程序(比如一个 Node.js 服务),父进程收到 SIGTERM 时,子进程还在跑——这会导致“假退出”。
立即学习“go语言免费学习笔记(深入)”;
- 必须显式调用
cmd.Process.Signal(syscall.SIGTERM),而不是依赖cmd.Process.Kill()(后者发的是SIGKILL,不可捕获) - 注意顺序:先发信号给子进程,再等待其退出;否则可能子进程还没响应,父进程就已退出
- Windows 下不能发
SIGTERM,应改用os.Process.Signal发送os.Interrupt,或调用TerminateProcess(需golang.org/x/sys/windows)
跨平台信号处理最容易被忽略的兼容性点
不是信号类型,而是信号语义。比如 SIGHUP 在 Linux 上常用于重载配置,但在 Windows 上没有对应概念——即使你写了 syscall.SIGHUP,Windows 编译器不会报错,但运行时完全不生效,且无任何提示。
- 真正可移植的信号只有两个:
os.Interrupt(Ctrl+C)、syscall.SIGTERM(Linux/macOS)——后者在 Windows 上被忽略,但不会 crash - 所有其他信号(
SIGHUP、SIGUSR1、SIGPIPE)都应视为平台专属,若要用,必须配合构建标签(//go:build linux)隔离文件 - 测试必须在目标平台做:在 macOS 上跑通的信号逻辑,放到 Windows 容器里可能完全不触发


















