為什麼 signal.Notify 要使用 buffered channel

golang logo

如果不了解什麼是 buffer 或 unbuffer channel 的朋友們,可以參考這篇文章先做初步了解,本文要跟大家介紹為什麼 signal.Notify 要使用 buffered channel 才可以,底下先來看看如何使用 signal.Notify,當我們要做 graceful shutdown 都會使用到這功能,想要正常關閉服務或連線,透過 signal 可以偵測訊號來源,執行後續相關工作 (關閉 DB 連線,檢查 Job 是否結束 … 等)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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)
}

上面例子可以很清楚看到說明,假如沒有使用 buffered channel 的話,你有一定的風險會沒抓到 Signal。那為什麼會有這段說明呢?底下用其他例子來看看。

教學影片

使用 unbuffered channel

將程式碼改成如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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 來測試

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 func,可以看到部分程式碼

1
2
3
4
5
6
7
8
9
    for c, h := range handlers.m {
        if h.want(n) {
            // send but do not block for it
            select {
            case c <- sig:
            default:
            }
        }
    }

可以看到上述程式碼,如果使用 unbuffered channel,那麼在五秒內接收到的任何訊號,都會跑到 default 條件內,所以造成 Channel 不會收到任何值,這也就是為什麼五秒內的任何動作,在五秒後都完全收不到。為了避免這件事情,所以通常我們會將 signal channel 設定為 buffer 1,來避免需要中斷程式時,確保主程式可以收到一個 signal 訊號。