欢迎,来自IP地址为:35.170.64.36 的朋友


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,这样可以确保至少收到一条系统中断消息。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注