最近搞一个agent,需要完成一个类似于
./agent start
./agent stop
当agent start之后,希望通过./agent stop终止agent的运行。
这本质上是一个进程间通信问题,这里我用到了信号signal:syscall.SIGKILL 以及 syscall.SIGTERM
用于监听信号的主进程代码如下:
signal.Notify(c, syscall.SIGKILL, syscall.SIGTERM)
此时用gofmt运行了一把发现报了这个玩意儿出来:
syscall.SIGKILL cannot be trapped
这个是什么意思捏?这里不得不说到kill -9 与 kill -15的区别了,
-9就是发送syscall.SIGKILL 信号,-15就是发送syscall.SIGTERM信号,这个可以在代码里点开看下:
16进制表示0x9 就是9,0xf 就是15
这两个到底区别在哪里呢?
-9 指定 kill 强制终止进程,这在某种情况下可能会造成问题,常常是进程所占有的某些资源没有正常释放的问题。
可以使用 kill 的默认发送信号 SIGTERM 来让进程正常终止。
《APUE》 中对 SIGTERM 信号的讲解内容如下:
这是 kill 命令在默认情况下发送的终止信号。因为它可以被应用程序捕捉到,因此使用 SIGTERM 能够给应用程序一个在 exit 之前执行回收工作优雅终止的机会(与 SIGKILL 正相反, SIGKILL 不能被捕捉到或忽略)。
早在几年前我其实说听说过-15比-9要优雅,到底优雅在什么地方,为什么SIGTERM可以在exit之前执行回收工作?为什么SIGKILL不行呢?
直接上代码~
主进程的代码:
func StartDaemon() {
c := make(chan os.Signal, 1)
// SIGKILL will force kill the Progress,Unable to handle and ignore the signal.
// SIGTERM elegant, allowing to catch signals and do processing after exit
signal.Notify(c, syscall.SIGKILL, syscall.SIGTERM)
<-c
fmt.Printf("这里执行了")
// there maybe send the status to the server in the future
os.Exit(0)
}
发送kill信号的代码:
// 这里的process就是上面的主进程结构体
err := process.Signal(syscall.SIGKILL)
当发送SIGKILL信号到主进程时,实际上
fmt.Printf(“这里执行了”) 这句话是不会执行的。因为KILL信号会直接立即终止该进程。
然而如果
发送kill信号的代码改为:
// 这里的process就是上面的主进程结构体
err := process.Signal(syscall.SIGTERM)
fmt.Printf(“这里执行了”) 这条是会执行的。这里就真的明白了所谓的优雅退出的问题。
我理解 除SIGKILL以外,其他信号包括SIGTERM,只是一个规范,这个信号是可以捕获后,写代码的人可以根据接收到的信号进行后续处理。所以所谓的“优雅”退出,其实是如果程序员写的代码,在有需要的条件下(例如停止主进程前,需要进行一些文件清理、连接关闭相关的事情) 在接收到信号之后,可以处理上述的一些逻辑,然后自己再主动调用os.exit(0)退出,而kill信号是直接退出不会理会程序员的后续逻辑了。
所以人云亦云:kill -15 pid更加优雅,kill -9 pid强制退出不够优雅,这句话现在应该理解为,如果程序员的代码里实现了-15的信号处理逻辑,在stop任务前去完成一些相关后续任务,这就是优雅的。如果程序员没有实现这个逻辑 -15有何用?所以是否优雅退出 重心应该是在程序员实现一个程序的逻辑里,而不是-15 或者-9 谁更优雅。