signal.Notify 必须显式调用,否则进程直接退出;需在长期运行逻辑前注册,监听 SIGINT、SIGTERM、SIGHUP 等信号,通道至少缓冲 1。

signal.Notify 必须显式调用,否则信号直接杀进程
Go 不会自动捕获 SIGINT 或 SIGTERM —— 如果没调 signal.Notify,你按 Ctrl+C 或执行 kill -15 $PID,程序就立刻退出,清理逻辑根本没机会跑。
常见错误是把 signal.Notify 写在 http.Serve 之后,或者只监听 os.Interrupt(它只是 SIGINT 的别名,不包含 SIGTERM)。
- 必须在主 goroutine 启动后、长期运行逻辑(如
http.ListenAndServe)之前注册 - 推荐写法:
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP),显式列出信号更安全、可移植 - 别依赖
signal.Notify(sigChan)默认监听所有信号——它不包括SIGKILL和SIGSTOP,但会混入一些冷门信号,反而增加干扰
通道要带缓冲,否则快速连发信号会丢
make(chan os.Signal, 1) 是底线。无缓冲通道(make(chan os.Signal))在第一次信号到达后就会阻塞,如果这时还没来得及 ,后续信号会被内核静默丢弃——比如你连按两次 Ctrl+C,或运维脚本里 <code>kill -TERM; sleep 0.1; kill -TERM,第二发就没了。
缓冲区设为 1 已足够:信号不是流数据,而是“事件通知”,我们只需要知道“有信号来了”,不需要排队记录多次。
立即学习“go语言免费学习笔记(深入)”;
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 别设更大缓冲(如 10),容易掩盖逻辑缺陷——比如 goroutine 挂了却没人读通道,信号越积越多,最后误以为还在响应
- 监听多个信号时,缓冲区仍只需 1:因为
signal.Notify对同一通道的多次信号投递,只保留最新一次(前提是通道未被消费)
收到信号后不能直接 os.Exit,得让 main 主动退出
在 signal handler 里调 os.Exit(1) 看似干脆,但它会跳过所有 defer、关闭文件、flush 日志,甚至中断正在运行的 goroutine,导致数据库连接没归还、临时文件没清理、指标没上报。
正确做法是让 main() 函数自己结束,在末尾统一调 os.Exit。
- 用
select配合信号通道阻塞主流程,收到信号后跳出循环,执行 defer 清理,再调os.Exit - 别在 goroutine 里调
os.Exit:它只对当前 goroutine 生效,main 还在跑,进程不会退 - 需要不同退出码(如
SIGINT → 130)?查syscall.SIGINT对应编号,手动映射:os.Exit(128 + int(syscall.SIGINT))
优雅关机失败,90% 是因为外部调用没设超时
http.Server.Shutdown 超时,并不是函数本身卡住,而是你的 handler 里有没受 context 控制的操作:比如一个没带 ctx 的 http.Client.Do、没用 db.QueryRowContext 的 SQL 查询、或者死等某个 channel。
Shutdown 只做两件事:停 listener、等已有 handler 返回。它不会帮你 cancel 卡住的 goroutine。
- 所有外部依赖必须显式传入 context:HTTP client、DB query、Redis 调用、time.Sleep 都得配合
ctx.Done() - 给
Shutdown的 context 设 10–30 秒超时,太短容易误杀,太长拖慢发布节奏 - 调试时加日志:
log.Printf("shutdown started at %v", time.Now()),确认是不是真进了 shutdown 流程,而不是 panic 或信号压根没捕获到
最常被忽略的是:信号处理和 Shutdown 之间没有时序保障。必须确保 signal.Notify 在 http.Serve 之前完成,且主 goroutine 不能提前 return —— 否则进程直接退出,Shutdown 根本没机会执行。

















