
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: %v\n", sig)
// 可在此处添加优雅退出逻辑,如关闭连接、保存状态等
if sig == syscall.SIGTERM || sig == syscall.SIGINT {
fmt.Println("Shutting down gracefully...")
done <- true
return
}
}
}()
fmt.Println("Application running. Send signals with: kill - ")
<-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 哲学的可靠信号处理机制。









