signal.notify 默认收不到 sigint,因 go 程序收到该信号会直接退出;需在 main 开头尽早注册带缓冲通道,并用 server.shutdown 配合超时上下文安全关闭 http server。

signal.Notify 为什么收不到 SIGINT?
默认情况下,Go 程序收到 SIGINT(Ctrl+C)会直接退出,不会触发 signal.Notify。必须显式用 signal.Notify 注册监听,且注册要早于可能触发信号的逻辑(比如启动 HTTP server 之前)。
常见错误是把 signal.Notify 放在 goroutine 里延迟调用,或放在 http.ListenAndServe 之后——此时主 goroutine 已阻塞,信号通道永远没机会读取。
- 必须在主 goroutine 中尽早调用
signal.Notify,推荐紧接在main()开头 - 监听通道必须是带缓冲的(如
make(chan os.Signal, 1)),否则第一次信号就可能丢失 - 只监听明确需要处理的信号,避免覆盖 Go 运行时默认行为(如
SIGQUIT用于 panic stack dump)
如何安全关闭 HTTP Server 并等待活跃连接?
监听到 SIGTERM 或 SIGINT 后,不能直接调用 server.Close(),这会立即中断所有连接。应使用 server.Shutdown() 配合上下文控制超时。
关键点在于:Shutdown 不会主动关闭监听套接字,而是先关闭 listener,再逐个等待已建立连接完成处理。若不设超时,程序可能永久 hang 住。
- 用
context.WithTimeout(context.Background(), 10*time.Second)控制最大等待时间 - 调用
server.Shutdown()前,需确保 listener 已通过net.Listener.Close()或自然结束(ListenAndServe返回)释放 - 务必 recover
http.ErrServerClosed错误,这是Shutdown正常完成的标志,不是异常
多个信号共用一个 channel 怎么区分?
signal.Notify 允许把多个信号发往同一 channel,但 channel 只传 os.Signal 接口值,需类型断言或 fmt.Sprint 判断具体信号。直接用 switch sig.(type) 不可靠,因为不同系统下底层实现不同。
最稳妥的方式是用 syscall.Signal 类型比较,或用 signal.String()(Go 1.16+)转为字符串匹配:
sig :=
- 不要依赖
sig.String() == "interrupt"这类硬编码字符串,跨平台不一致 - 若需同时响应
SIGHUP(重载配置)和SIGTERM(优雅退出),建议分两个 channel 处理,逻辑更清晰 - 注意
SIGUSR1/SIGUSR2在 Windows 不可用,仅限 Unix-like 系统
为什么 defer signal.Stop() 没用?
signal.Stop() 用于取消监听,但若写在 defer 里,往往来不及执行——主 goroutine 在收到信号后可能已退出,defer 不触发;或者 shutdown 完成后程序直接 exit,stop 被跳过。
真正需要 stop 的场景极少。多数服务只需监听一次信号,收到即开始退出流程。强行 stop 反而可能干扰其他组件(如测试框架或嵌入式 runtime)的信号处理。
- 除非你在同一个 channel 上多次调用
Notify(比如热重载时重新绑定),否则无需调用Stop - 如果真要 stop,应在 shutdown 流程末尾、确认不再接收新信号时显式调用,而非 defer
- Go 1.16+ 中
signal.Ignore和signal.Reset更适合清理,但日常服务几乎用不到
context.WithTimeout 不一定能中断它们。这时候得在 handler 内部主动检查 context 是否 Done,并提前返回。信号处理只是入口,真正的“优雅”藏在每个可中断的环节里。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











