为什么 signal.Notify 使用缓冲通道?

在本文中,我们将向您介绍为什么signal.Notify要使用缓冲通道。当我们想做优雅的关闭时,我们会使用这个功能来正常关闭服务或连接。通过signal,我们可以检测到信号的来源,并进行后续工作(关闭DB连接,检查工作是否完成……等)。

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)

上面的示例清楚地表明,如果您不使用缓冲通道,则存在一定的无法捕获信号的风险。那么为什么会有这样的描述呢?让我们看看其他例子。

使用无缓冲通道

将代码更改为以下内容:

package main

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

func main() {
    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,你会看到Got signal: interrupt,那么如果在接受channle之前我们还有一些很复杂的工作要做会发什么,先time.Sleep用来测试一下。

package main

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

func main() {
    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,理论上你会在五秒后正常收到第一个信号。让我们看看为什么。

形成原因

我们打开Golang的singal.go文件,找到process函数,可以看到部分代码:

for c, h := range handlers.m {
    if h.want(n) {
        // send but do not block for it
        select {
        case c <- sig:
        default:
        }
    }
}

在上面的代码中可以看到,如果使用无缓冲通道,5秒内收到的任何信号都会运行到默认状态,所以通道不会收到任何值,这就是为什么5秒内的任何动作都不会收到的原因。为了避免这种情况,我们通常将信号通道设置为缓冲区1,以避免打断程序的执行,以确保主程序可以接收到信号。

你可能感兴趣的:(go)