Go语言signal包使用指南

go语言学习笔记:signal包

os/signal主要用于实现对信号的处理(官方文档)

信号类型

首先关于linux信号机制课自行查找资料,这里不再赘述。linux中的全部信号如下图:

信号

在go中预定义了几种信号:

//"syscall"
const (
	// More invented values for signals
	SIGHUP  = Signal(0x1)
	SIGINT  = Signal(0x2)
	SIGQUIT = Signal(0x3)
	SIGILL  = Signal(0x4)
	SIGTRAP = Signal(0x5)
	SIGABRT = Signal(0x6)
	SIGBUS  = Signal(0x7)
	SIGFPE  = Signal(0x8)
	SIGKILL = Signal(0x9)
	SIGSEGV = Signal(0xb)
	SIGPIPE = Signal(0xd)
	SIGALRM = Signal(0xe)
	SIGTERM = Signal(0xf)
)

首先在这么多信号中,SIGKILL和SIGSTOP是无法被程序捕获的,其中SIGKILL就是我们常用的kill -9 pid方法锁触发的。其次一些有程序执行中的错误所触发的同步信号如SIGBUS,SIGFPE和SIGSEGV,go会将其转为panic,不过若是我们通过kill方式触发也是可以被捕获的。

除了那些同步信号,其余都是异步信号,是由内核或其他程序发送的,我们都可以捕获。

在异步信号中,当程序丢失终端时收到SIGHUP,在终端按下中断字符(一般为ctrl+c)时收到SIGINT,在终端按下退出字符(一般为^\)时收到SIGQUIT。

正常的,信号都是有默认动作的,最常见的如按下ctrl+c退出程序。其余的SIGHUP,SIGINT或SIGTERM信号导致程序退出。SIGQUIT,SIGILL,SIGTRAP,SIGABRT,SIGSTKFLT,SIGEMT或SIGSYS信号导致程序以堆栈转储退出。SIGTSTP,SIGTTIN或SIGTTOU信号获取系统默认行为(shell使用这些信号进行作业控制)。SIGPROF会被go运行时捕获实现runtime.CPUProfile.

捕获信号

signal包中提供了Notify方法,用于注册所要监听的信号。

func Notify(c chan<- os.Signal, sig ...os.Signal)

该方法需要提供一个Signal类型的channel,以及要监听的信号(当不指定时会监听所有信号)。当有信号到来时,会被写入所传入的channel中,之后拿出来即可。下例是一个监听ctrl+c退出的程序:

func main() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT)

	s := <-c
	fmt.Println("Got signal:", s)
}

上述程序会在第五行阻塞,直到有一个信号过来。运行结果如下:

其余api

func Stop(c chan<- os.Signal)

这个方法用于停止监听信号,之后不会再往所指定的channel中写入任何内容。搭配Notify使用如下:

go func() {
		sigc := make(chan os.Signal, 1)
		signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) //监听信号
		defer signal.Stop(sigc) //确保关闭
		<-sigc  //线程阻塞
		log.Info("Got interrupt, shutting down...")
		go Stop() //执行程序停止逻辑
	}()

func Ignore(sig …os.Signal)

忽略指定的信号,同理,若是未指定,忽略所有信号

func Ignored(sig os.Signal) bool

检查某个信号是否被忽略

func Reset(sig …os.Signal)

重置之前调用Notify时的处理。也就是不在捕获信号,进行默认操作。注意和Ignore的区别,Ignore是不对信号做任何操作,Reset是恢复默认操作。

附录

部分信号说明(图片来源于网络):

信号.png