
go 程序需通过循环读取信号通道才能持续响应 `kill` 命令发送的各类信号(如 sigterm、sigint),否则仅首次信号(如 ctrl+c)生效,后续信号将被忽略。
在 Go 中,os/signal 包用于监听 操作系统 信号,但其行为高度依赖通道消费方式。原始代码中,goroutine 仅执行一次 igs 操作后即退出,导致信号通道后续接收的信号无人消费——这些信号虽已成功发送并被内核投递到 Go 运行时,却因通道缓冲区满(容量为 1)且无消费者而被静默丢弃。
正确做法是使用 for 循环持续从信号通道读取,确保每次信号到达都能被及时处理:
package main import ("fmt" "os" "os/signal" "syscall") func main() { sigs := make(chan os.Signal, 1) done := make(chan bool, 1) // 监听常见终止类信号;可显式指定以提高可读性 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1) go func() { for { sig := <-sigs fmt.printf("received signal: %vn", sig)> ") <-done fmt.Println("Exiting cleanly.") }
关键注意事项:
- signal.Notify(sigs) 若不传入具体信号,默认监听所有可捕获的同步信号(含 SIGUSR1、SIGUSR2 等),但 不包括 SIGKILL(9)和 SIGSTOP(19)——二者无法被捕获或忽略,由内核强制执行;
- 通道缓冲区大小设为 1 足够应对大多数场景,但若需防止高频率信号丢失,可适当增大(如 make(chan os.Signal, 10)),并配合 select + default 实现非阻塞消费;
- 生产环境应避免仅依赖 fmt.Println 做信号响应,建议集成日志库、触发上下文取消(context.WithCancel)、执行资源清理,并设置超时强制退出;
- 使用 syscall 显式指定信号(如 syscall.SIGTERM)比依赖数字更安全、可移植且语义清晰。
通过上述改进,程序即可稳定响应 kill -15 $PID(SIGTERM)、kill -2 $PID(SIGINT)、kill -1 $PID(SIGHUP)等命令,实现符合 Unix 哲学的可靠信号处理机制。






























