Golang中的信号处理

    1.信号类型

       每个平台的信号定义或许有些不同。下面列出了POSIX中定义的信号。

       Linux 使用34-64信号用作实时系统中。

       命令 man signal 提供了官方的信号介绍。

 

       在POSIX.1-1990标准中定义的信号列表

信号动作说明
SIGHUP1Term终端控制进程结束(终端连接断开)
SIGINT2Term用户发送INTR字符(Ctrl+C)触发
SIGQUIT3Core用户发送QUIT字符(Ctrl+/)触发
SIGILL4Core非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT6Core调用abort函数触发
SIGFPE8Core算术运行错误(浮点运算错误、除数为零等)
SIGKILL9Term无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV11Core无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE13Term消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM14Term时钟定时信号
SIGTERM15Term结束程序(可以被捕获、阻塞或忽略)
SIGUSR130,10,16Term用户保留
SIGUSR231,12,17Term用户保留
SIGCHLD20,17,18Ign子进程结束(由父进程接收)
SIGCONT19,18,25Cont继续执行已经停止的进程(不能被阻塞)
SIGSTOP17,19,23Stop停止进程(不能被捕获、阻塞或忽略)
SIGTSTP18,20,24Stop停止进程(可以被捕获、阻塞或忽略)
SIGTTIN21,21,26Stop后台程序从终端中读取数据时触发
SIGTTOU22,22,27Stop后台程序向终端中写数据时触发

   

       在SUSv2和POSIX.1-2001标准中的信号列表

信号动作说明
SIGTRAP5CoreTrap指令触发(如断点,在调试器中使用)
SIGBUS0,7,10Core非法地址(内存地址对齐错误)
SIGPOLL TermPollable event (Sys V). Synonym for SIGIO
SIGPROF27,27,29Term性能时钟信号(包含系统调用时间和进程占用CPU的时间)
SIGSYS12,31,12Core无效的系统调用(SVr4)
SIGURG16,23,21Ign有紧急数据到达Socket(4.2BSD)
SIGVTALRM26,26,28Term虚拟时钟信号(进程占用CPU的时间)(4.2BSD)
SIGXCPU24,24,30Core超过CPU时间资源限制(4.2BSD)
SIGXFSZ25,25,31Core超过文件大小资源限制(4.2BSD)
第1列为信号名;
第2列为对应的信号值,需要注意的是,有些信号名对应着3个信号值,这是因为这些信号值与平台相关,将man手册中对3个信号值的说明摘出如下,the first one is usually valid for alpha and sparc, the middle one for i386, ppc and sh, and the last one for mips.
第3列为操作系统收到信号后的动作,Term表明默认动作为终止进程,Ign表明默认动作为忽略该信号,Core表明默认动作为终止进程同时输出core dump,Stop表明默认动作为停止进程。
第4列为对信号作用的注释性说明,浅显易懂,这里不再赘述。
需要特别说明的是,SIGKILL和SIGSTOP这两个信号既不能被应用程序捕获,也不能被操作系统阻塞或忽略。

    kill pid与kill -9 pid的区别

      kill pid的作用是向进程号为pid的进程发送SIGTERM(这是kill默认发送的信号),该信号是一个结束进程的信号且可以被应用程序捕获。若应用程序没有捕获并响应该信号的逻辑代码,则该信号的默认动作是kill掉进程。这是终止指定进程的推荐做法。

      kill -9 pid则是向进程号为pid的进程发送SIGKILL(该信号的编号为9),从本文上面的说明可知,SIGKILL既不能被应用程序捕获,也不能被阻塞或忽略,其动作是立即结束指定进程。通俗地说,应用程序根本无法“感知”SIGKILL信号,它在完全无准备的情况下,就被收到SIGKILL信号的操作系统给干掉了,显然,在这种“暴力”情况下,应用程序完全没有释放当前占用资源的机会。事实上,SIGKILL信号是直接发给init进程的,它收到该信号后,负责终止pid指定的进程。在某些情况下(如进程已经hang死,无法响应正常信号),就可以使用kill -9来结束进程。

从上面的介绍不难看出,优雅退出可以通过捕获SIGTERM等信号来实现。具体来讲,通常只需要两步动作:

1)注册SIGTERM信号的处理函数并在处理函数中做一些进程退出的准备。信号处理函数的注册可以通过signal()或sigaction()来实现,其中,推荐使用后者来实现信号响应函数的设置。信号处理函数的逻辑越简单越好,通常的做法是在该函数中设置一个bool型的flag变量以表明进程收到了SIGTERM信号,准备退出。

2)在主进程的main()中,可以使用os/signal包捕获SIGTERM等信号,然后释放资源或dump进程当前状态或记录日志的动作,完成这些后,主进程退出。

需要特别注意的,如果想要优雅退出,在重启脚本或关闭程序的时候不能用kill -9 pid 或killall -9 name来强制杀掉进程。

 

Go中的Signal发送和处理

  golang中对信号的处理主要使用os/signal包中的两个方法:

   notify方法用来监听收到的信号

   stop方法用来取消监听

示例一: (example1.go)

package main

import (
	"os"
	"os/signal"
	"fmt"
)

func main() {
	c := make(chan os.Signal)
	//监听所有信号
	signal.Notify(c)
	fmt.Println("start!")
	s := <- c
	fmt.Println("stop,signal : ",s)
}

  执行结果如下:

go build example1.go 得到可执行文件example1

./example1
start!
ctrl + c 输出
stop,signal :  interrupt


./example1
start!
kill  pid  输出
stop,signal :  terminated

 

示例二: (example2.go)

package main

import (
	"os"
	"os/signal"
	"go-common/library/syscall"
	"fmt"
)

func main() {
	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt,os.Kill,syscall.SIGUSR1,syscall.SIGUSR2)
	fmt.Println("start!")
	s := <-c
	fmt.Println("stop,signal:",s)
}
go build example2.go 得到可执行文件example2

./example2
start!
ctrl+c  输出
stop,signal: interrupt

./example2
start!
kill pid 输出
stop,signal: terminated

所以,我们可以通过os/signal包捕获SIGTERM等信号,然后实现优雅退出