根本原因是信号传播未屏蔽、Shutdown未等完、主goroutine过早退出;必须在子进程启动前调用signal.Ignore(syscall.SIGUSR2),确保channel提前初始化,所有阻塞操作带context,且主goroutine阻塞等待Shutdown完成。

Go 里用 signal.Notify 捕获 SIGUSR2 做平滑重启,进程却总被直接杀掉——根本原因不是信号没收到,而是三个关键环节漏了任何一个:信号传播没屏蔽、http.Server.Shutdown 没等完、主 goroutine 过早退出。
为什么 signal.Notify 注册了却收不到 SIGUSR2?
常见错觉是“注册了就一定能收到”,但实际在 fork 子进程后,SIGUSR2 会默认传播到所有子进程(比如用 exec.Command 启动的新实例),导致新老进程互相干扰甚至同时 shutdown。
- 必须在子进程启动前调用
signal.Ignore(syscall.SIGUSR2),否则新进程一启动就收到信号,立刻开始 shutdown,和父进程抢 listener -
signal.Notify的 channel 必须在http.Server.Shutdown调用前就初始化好;如果 signal 来了但 channel 还没 ready,信号就丢了 - macOS 不支持用户进程接收
SIGUSR2(报operation not permitted),开发环境建议统一改用SIGHUP或SIGTERM
http.Server.Shutdown 总是超时失败?
不是函数有问题,而是你没控制住它依赖的上下文链路。Shutdown 只负责停止 accept 新连接 + 等待已有连接 close,但它不等你业务里没设 timeout 的 HTTP 调用或 DB 查询。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 所有外部调用必须带 context:用
http.Client.Do(req.WithContext(ctx)),DB 查询用db.QueryContext(ctx, ...) - 超时不能硬写 5 秒——长轮询、WebSocket、大文件上传可能卡住;建议设 10–30 秒,并加日志:
log.Printf("shutdown took %v", time.Since(start)) - Shutdown 返回后,别急着 exit;要确保所有后台 goroutine(比如 metrics 上报、日志 flush)也通过同一 context cancel 掉
主 goroutine 退出后整个进程就没了?
这是最隐蔽的坑:监听到信号、调用了 Shutdown、打印了 “shutting down...”,然后 return —— main 函数结束,整个进程立即终止,不管 Shutdown 是否完成。
立即学习“go语言免费学习笔记(深入)”;
- 不能在 signal 处理完就 return;得用
select {}或sync.WaitGroup卡住主 goroutine - 如果你用
os/exec启动子进程,记得在父进程中用cmd.Process.Signal(syscall.SIGTERM)主动 kill 自己,而不是靠 OS 杀 - 子进程启动成功后,父进程要等
Shutdown完成再退出;否则旧连接被强制断开,客户端看到 connection reset
真正难的不是写几行 signal.Notify,而是让所有 goroutine 都响应同一个 context、所有资源释放都有明确出口、所有超时都分层可控——漏掉任意一层,平滑就变成“假装平滑”。

















