
go 程序默认无法持续响应 `kill` 命令发送的信号(如 sigterm、sigint),根本原因是信号接收协程在处理一次信号后即退出;需通过循环阻塞读取通道,才能持续捕获后续信号。
在 Go 中使用 os/signal 包监听系统信号时,一个常见误区是:仅执行一次 igs 操作,导致协程在接收首个信号(例如 CTRL+C 触发的 os.Interrupt)后立即返回,后续通过 kill -15
✅ 正确做法是:在 goroutine 中使用 无限 for 循环 + 阻塞接收,确保信号通道持续被消费:
package main
import (
"fmt"
"os"
"os/signal"
)
func main() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
// 注册所有默认可捕获信号(等价于 signal.Notify(sigs, os.Interrupt, os.Kill, syscall.SIGTERM, ...))
signal.Notify(sigs)
go func() {
for { // 关键:循环持续接收,避免协程退出
sig := <-sigs
fmt.Printf("Received signal: %v\n", sig)
// ✅ 此处可加入优雅退出逻辑,如关闭资源、等待任务完成等
if sig == os.Interrupt || sig == os.Kill {
fmt.Println("Shutting down gracefully...")
done <- true
return
}
}
}()
fmt.Println("Waiting for signals...")
<-done
fmt.Println("Exiting cleanly.")
}? 注意事项:
- signal.Notify(sigs) 不传信号类型时,默认监听 os.Interrupt(Ctrl+C)和 os.Kill(不可捕获,仅用于 os.Exit),但不包含 SIGTERM(kill -15)或 SIGHUP 等常用信号。生产环境应显式指定:
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
- os.Kill 无法被拦截(会强制终止进程),真正可用于优雅退出的是 syscall.SIGTERM(kill -15)和 os.Interrupt(Ctrl+C)。
- 主 goroutine 必须保持运行(如
- 若需多信号差异化处理(如 SIGUSR1 触发日志轮转,SIGTERM 触发退出),可在 for 循环内用 switch sig 分支判断。
✅ 测试验证:编译运行后,在另一终端执行:
$ ./signal & $ kill -15 $! # → 输出 "Received signal: terminated" $ kill -USR1 $! # → 输出 "Received signal: user defined signal 1"
掌握这一模式,是构建高可靠性 Go 后台服务(如 Web 服务器、守护进程)的基础能力。









