go中的信号量
有些信号名对应着3个信号值,这是因为这些信号值与平台相关,SIGKILL和SIGSTOP这两个信号既不能被应用程序捕获,也不能被操作系统阻塞或忽略。
kill pid的作用是向进程号为pid的进程发送SIGTERM(这是kill默认发送的信号),该信号是一个结束进程的信号且可以被应用程序捕获。若应用程序没有捕获并响应该信号的逻辑代码,则该信号的默认动作是kill掉进程。这是终止指定进程的推荐做法。
kill -9 pid则是向进程号为pid的进程发送SIGKILL(该信号的编号为9),从本文上面的说明可知,SIGKILL既不能被应用程序捕获,也不能被阻塞或忽略,其动作是立即结束指定进程。通俗地说,应用程序根本无法“感知”SIGKILL信号,它在完全无准备的情况下,就被收到SIGKILL信号的操作系统给干掉了,显然,在这种“暴力”情况下,应用程序完全没有释放当前占用资源的机会。事实上,SIGKILL信号是直接发给init进程的,它收到该信号后,负责终止pid指定的进程。在某些情况下(如进程已经hang死,无法响应正常信号),就可以使用kill -9来结束进程。
若通过kill结束的进程是一个创建过子进程的父进程,则其子进程就会成为孤儿进程(Orphan Process),这种情况下,子进程的退出状态就不能再被应用进程捕获(因为作为父进程的应用程序已经不存在了),不过应该不会对整个linux系统产生什么不利影响。
在长时间的程序运行过程中,可能有大量的系统资源被申请,无论在以何种方式退出前,他们应该及时将这些资源释放并将状态输出到日志中方便调试和排错。
signal.Notify方法监听和捕获信号量
func Notify(c chan<- os.Signal, sig …os.Signal)
首先定义一个chan传递信号量,然后说明那些信号量是需要被捕获的(不填的话就默认捕获任何信号量)
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
监听信号量输出(只要上述三种信号量有输出,就会停止阻塞,执行代码):
func terminal() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
select {
case <-c:
log.Infof("stop signal caught, pid[%d] stopping...", os.Getpid())
}
http.Shutdown()
rpc.Shutdown()
log.Info("transfer has stopped successfully !!!")
}
另一种方式可以根据不同信号量捕获做不同的逻辑处理,代码如下
func terminal() {
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
select {
switch sig {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Panic("SIGQUIT")
case syscall.SIGHUP:
log.Panic("SIGHUP")
case syscall.SIGHUP:
log.Panic("SIGINT")
}
http.Shutdown()
rpc.Shutdown()
log.Info("transfer has stopped successfully !!!")
}
terminal 方法放到 main.go 的最后一行执行,一直阻塞直到捕获到对应信号量做终止进程的相关后续处理。