欢迎,来自IP地址为:18.97.14.86 的朋友
signal.Notify 是 golang 中对信号处理的一个方法,用来监听收到的信号,取消监听时使用 stop 方法。本文介绍为什么要使用 buffered channel 来做监听,之后会演示如何使用 signal.Notify。当我们想优雅的退出守护进程时,一般都会使用到这功能,例如正常关闭服务,通过 signal 可以侦测消息来源,并执行后续相关工作 (关闭数据库连接、检查任务是否结束等)。
首先以下面的程序举例:
package main import ( "fmt" "os" "os/signal" ) func main() { // Set up channel on which to send signal notifications. // We must use a buffered channel or risk missing the signal // if we're not ready to receive when the signal is sent. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) // Block until a signal is received. s := <-c fmt.Println("Got signal:", s) }
程序中使用 os/signal 包的 signal.Notify 方法用于侦听系统消息,并设置了一个 buffered channel 用于接收系统中断。当有系统中断产生时,程序会打印接收到系统中断并退出。
程序执行过程如下:
如果将之前的 buffered channel 修改为 unbuffered,如下所示:
package main import ( "fmt" "os" "os/signal" ) func main() { // Set up channel on which to send signal notifications. // We must use a buffered channel or risk missing the signal // if we're not ready to receive when the signal is sent. c := make(chan os.Signal) signal.Notify(c, os.Interrupt) // Block until a signal is received. s := <-c fmt.Println("Got signal:", s) }
然后再按 Ctrl+c 中断程序,同样会得到与使用 buffered channel 同样的结果。这似乎没有什么问题,使用 buffered 和 unbuffered 好像也没有区别。但是如果对程序做一些修改,让 main() 程序在接收中断前做一些其他比较耗时的任务,例如 time.sleep,就会引发问题:
package main import ( "fmt" "os" "os/signal" ) func main() { // Set up channel on which to send signal notifications. // We must use a buffered channel or risk missing the signal // if we're not ready to receive when the signal is sent. c := make(chan os.Signal) signal.Notify(c, os.Interrupt) time.Sleep(5*time.Second) // Block until a signal is received. s := <-c fmt.Println("Got signal:", s) }
现在执行程序,会发现在前五秒内,无论我们按多少次 Ctrl+c,程序都不会中止,等到五秒结束后,需要再次按 Ctrl+c 程序才会中止,这与我们预期的在五秒内按过一次 Ctrl+c,之后到五秒时程序就会自动中止的结果差异很大,说明在五秒内的系统中断都没有被监听到。
造成这个问题在原因在于 signal.go 的 process 函数,其中处理消息部分有如下内容:
for c, h := range handlers.m { if h.want(n) { // send but do not block for it select { case c <- sig: default: } } }
可以看到,由于 unbuffered channel 长度为0,造成在五秒内收到的任何消息都会进入 default 条件,造成 channel 中没有任何值,从而接收不到中断消息。
为了避免该情况的出现,我们一般会将该通道设置为 buffer 1,这样可以确保至少收到一条系统中断消息。