Go言語におけるSignalのハンドリングについて
golangには、Linuxのシグナルをハンドリングする機能がある。
具体的には、os/signal
パッケージ/syscall
パッケージとchannel機能を使う。
func main() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) switch(sigCh) { case syscall.SIGTERM: fmt.Println("SIGTERM") case syscall.SIGINT: fmt.Println("SIGINT") } }
ただし、signalはlinux/unixにおける概念であり、WindowsでのCTRL^C
はキャッチできない。パッケージもsyscall
だし。
Windowsに対応させるには、os.Interrupt
を使う
(参考:signal - The Go Programming Language)
func main() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, os.Intterupt) switch(sigCh) { case syscall.SIGTERM: fmt.Println("SIGTERM") case syscall.SIGINT: fmt.Println("SIGINT") case os.Interrupt: fmt.Println("Interrupt") } }
これで対象のシグナルが発生すると、sigChにメッセージが通知されるようになる。 なお、sigChはchannelのため、読み出しを行うと、メッセージングされるまでブロッキングが発生する。
通常は何かを実行中のバックグランドでキャッチしたいはずなので、goroutineにする。ただし、goroutineはGCされないため、終了しないとリークしてしまうらしい(Go言語(Golang) はまりどころと解決策)。
なので、別途チャンネルを使って停止できるようにする。
このとき、ちゃんとgoroutineが終了するように、sync.WaitGroup
を使って同期を行う
package main import ( "fmt" "os" "os/signal" "syscall" "sync" ) func main() { wg := sync.WaitGroup{} doneCh := make(chan struct{}, 1) wg.Add(1) go func() { defer wg.Done() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, os.Intterupt) defer signal.Stop(sigCh) for { select { case sig := <-sigCh: switch(sig) { case syscall.SIGTERM: fmt.Println("SIGTERM") case syscall.SIGINT: fmt.Println("SIGINT") case os.Interrupt: fmt.Println("Interrupt") } case <-doneCh: fmt.Println("Done") return } } }() // 何かする // 終了 doneCh <- struct{}{} wg.Wait() }
context.Context
と組み合わせれば、CTRL^C
を受けてキャンセルを行ったりできる。
ただその場合、生成されたcontext.CancelFunc()
を、シグナルハンドラのgoroutineで直接呼び出すか、一層channelをはさむべきなのか、今一つ判断がつかない。