go 中捕获 ctrl+c 的正确信号类型是 os.interrupt,它在 unix 系统等价于 sigint、windows 对应 ctrl_c_event;必须用 signal.notify 注册并读取 chan os.signal,不可依赖 defer 或空循环,且需妥善清理 goroutine 和资源。

Go 中捕获 Ctrl+C 的正确信号类型是 os.Interrupt,不是 syscall.SIGINT
很多人误以为要直接用 syscall.SIGINT,其实 Go 标准库已将它封装为平台无关的 os.Interrupt —— 在 Unix/Linux/macOS 上等价于 SIGINT,在 Windows 上对应 CTRL_C_EVENT。硬写 syscall.SIGINT 会导致 Windows 编译失败或行为不一致。
关键点:
-
os.Interrupt是os.Signal类型的变量,可直接传给signal.Notify - 不要 import
"syscall"来获取中断信号,除非你明确需要其他底层信号 - Windows 下
os.Interrupt同样生效,但syscall.SIGINT未定义
必须用 signal.Notify 注册,不能靠 defer 或 panic 捕获
Ctrl+C 是异步操作系统信号,Go 运行时不会把它转成 panic,也不会触发 defer。唯一可靠方式是用 signal.Notify 将信号转发到一个 chan os.Signal。
常见错误写法:
// ❌ 错误:这根本收不到 Ctrl+C
defer func() {
fmt.Println("cleanup")
}()
正确做法:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt)
- channel 容量至少为 1,否则首次信号可能丢失(尤其在 handler 启动前)
- 注册后需主动读取 channel,否则信号会堆积或被忽略
- 若程序主 goroutine 退出,signal.Notify 自动失效,无需显式取消
阻塞主线程时要用 select + sigChan,别用 time.Sleep
很多示例用 time.Sleep(time.Hour) 模拟长期运行,但这会掩盖信号处理逻辑缺陷——信号来了,但没人读 sigChan,程序就卡死在 Sleep,Ctrl+C 无响应。
推荐结构:
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
<p>for {
select {
case </p>
-
select是唯一能同时等待信号和执行任务的安全方式 - 避免用
for {}空循环,会吃满 CPU;加time.Sleep或用更精确的事件驱动 - 如果主逻辑本身阻塞(如
http.Server.Serve),需另起 goroutine 处理信号,并调用server.Shutdown
清理资源时注意 goroutine 泄漏和竞态
收到 os.Interrupt 后常需关闭 listener、数据库连接、worker goroutine 等。但直接 return 或 os.Exit 会跳过 cleanup,而粗暴关 channel 可能导致 panic。
实操要点:
- 用
context.WithCancel控制子 goroutine 生命周期,收到信号后调用cancel() - 关闭
net.Listener会中断Accept调用,使其返回ErrClosed,需检查并退出 accept 循环 - 不要在信号 handler 里调用
os.Exit,它会跳过defer和runtime.SetFinalizer - 多个信号源(如同时监听
os.Interrupt和syscall.SIGHUP)时,确保 cleanup 只执行一次
真正麻烦的从来不是怎么收到 Ctrl+C,而是信号来了之后,那些没被 cancel 的 goroutine、没被 Close 的 conn、没被 sync.WaitGroup.Done 的 worker —— 它们才是让程序“看起来卡住”或“无法退出”的根源。










