kurumi-bioの雑記帳

プログラミング、パソコン、ペット、 犬、お出かけ

初心者のGo言語 -39- <NotifyContext,Reset,Stop>

こんにちは、kurumi-bioです。
第2回目のsignalパッケージ(標準ライブラリー)の学習です。

前回の記事

kurumi-bio.hatenablog.com

APIリファレンス(過去記事の一覧)

kurumi-bio.hatenablog.com

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20.3 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.3 linux/amd64

NotifyContext関数

func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)

関数の説明 NotifyContext は、リストされたシグナルの 1 つが到着したとき、返された stop 関数が呼び出されたとき、または親コンテキストの Done チャネルが閉じられたときのいずれかが先に起こったときに、完了とマークされた (その Done チャネルが閉じられている) 親コンテキストのコピーを返します。

stop 関数は信号の動作の登録を解除し、signal.Reset と同様に、特定の信号のデフォルトの動作を復元する場合があります。たとえば、os.Interrupt を受信した Go プログラムのデフォルトの動作は終了します。 NotifyContext(parent, os.Interrupt) を呼び出すと、返されたコンテキストをキャンセルするように動作が変更されます。今後受信した割り込みは、返された停止関数が呼び出されるまで、デフォルト (終了) 動作をトリガーしません。

stop 関数はそれに関連付けられたリソースを解放するため、このコンテキストで実行中の操作が完了し、シグナルをコンテキストに転送する必要がなくなったら、コードはすぐに stop を呼び出す必要があります。

◆テストコード

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    var ctx context.Context
    var stop context.CancelFunc

    fmt.Printf("pid=[%d]\n", os.Getpid())

    if len(os.Args) == 1 {
        ctx, stop = signal.NotifyContext(context.Background(), syscall.SIGTERM)
    } else {
        ctx, stop = signal.NotifyContext(context.Background(), syscall.SIGINT)
    }
    defer stop()

    select {
    case <-time.After(10 * time.Second):
        fmt.Println("missed signal")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
        stop()
    }
}

コードの説明 実行時の引数の有無で受けるシグナルを変えています。
引数が無い場合はsyscall.SIGTERMで、引数が有る場合はsyscall.SIGINTが到着するとコンテキストがキャンセルされます。

◆実行結果(Linux)

実行結果の説明 1回目は、引数無しで実行しているので、syscall.SIGTERMの到着を待っているためCtrl+Cを押しても コンテキストはキャンセルされませんでした。
2回目は、引数有で実行しているので、syscall.SIGINTの到着を待っており、Ctrtl+Cを押すとコンテキストがキャンセルされました。

Reset関数

func Reset(sig ...os.Signal)

関数の説明 Reset は、提供されたシグナルに対する以前の Notify 呼び出しの効果を取り消します。シグナルが提供されない場合、すべてのシグナル ハンドラーがリセットされます。

◆テストコード

package main

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

func main() {
    fmt.Printf("pid=[%d]\n", os.Getpid())

    signal.Ignore(syscall.SIGINT)

    c := make(chan os.Signal, 1)

    signal.Notify(c)
    s := <-c
    fmt.Printf("signal=[%s]\n", s)

    if len(os.Args) == 1 {
        signal.Reset(syscall.SIGINT)
    }
    s = <-c
    fmt.Printf("signal=[%s]\n", s)
}

コードの説明 syscall.SIGINTを無効にした状態で、シグナルの通知を2回受信して表示しています。
コマンド実行時の引数が無い場合は、2回目の受信時はsyscall.SIGINTを取り消し(signal.Reset)てます。

◆実行結果(Linux)

実行結果の説明 1回目の実行は引数無しなので、Ctrl+Cの受信が取り消されているためCtrl+Zを押すまで2回目のシグナルの通知を受信しませんでした。
2回目の実行は引数有なので、受信の取り消しが行われなかったため、Crtl+Cの通知を2回受信し処理が終了しました。

Stop関数

func Stop(c chan<- os.Signal)

関数の説明 Stop により、パッケージ信号が c への受信信号の中継を停止します。これにより、c を使用した以前の Notify 呼び出しの効果がすべて取り消されます。 Stop が返されると、c はそれ以上信号を受信しないことが保証されます。

◆テストコード

package main

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

func main() {
    fmt.Printf("pid=[%d]\n", os.Getpid())

    signal.Ignore(syscall.SIGINT)

    c := make(chan os.Signal, 1)

    signal.Notify(c)
    s := <-c
    fmt.Printf("signal=[%s]\n", s)

    if len(os.Args) == 1 {
        signal.Stop(c)
    }
    s = <-c
    fmt.Printf("signal=[%s]\n", s)
}

コードの説明 syscall.SIGINTを無効にした状態で、シグナルの通知を2回受信して表示しています。
コマンド実行時の引数が無い場合は、1回目の受信の後に信号の中継を停止(signal.Stop)しています。

◆実行結果(Linux)

実行結果の説明 1回目の実行は引数無しなので、1回目の受信後に信号の中継を停止したため、
2回目が受信できず処理が終わらなかったので、killコマンドで終了(Terminated)しました。
2回目の実行は引数有なので、信号の中継が停止されないため、シグナルの通知を2回受信し処理が終了しました。  

最後までご覧いただきありがとうございます