コンソールから送信されたCtrl+C(SIGINT
)信号をキャプチャして、一部の実行合計を出力します。
これはGolangで可能ですか?
注:初めて質問を投稿したとき、の代わりに混乱してCtrl+CいSIGTERM
ましたSIGINT
。
コンソールから送信されたCtrl+C(SIGINT
)信号をキャプチャして、一部の実行合計を出力します。
これはGolangで可能ですか?
注:初めて質問を投稿したとき、の代わりに混乱してCtrl+CいSIGTERM
ましたSIGINT
。
回答:
os / signalパッケージを使用して、着信信号を処理できます。Ctrl+ CはSIGINTなので、これをトラップに使用できますos.Interrupt
。
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
for sig := range c {
// sig is a ^C, handle it
}
}()
プログラムを終了して情報を出力する方法は、完全にあなた次第です。
go run
コンソールでを介してプログラムを実行し、^ Cを介してSIGTERMを送信すると、信号がチャネルに書き込まれ、プログラムが応答しますが、予期せずループから脱落したように見えます。これは、シグレムも行くからgo run
です!(これは私にかなりの混乱を引き起こしています!)
runtime.Gosched
、適切な場所(プログラムのメインループにある場合)で呼び出す必要があります
これは機能します:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time" // or "runtime"
)
func cleanup() {
fmt.Println("cleanup")
}
func main() {
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cleanup()
os.Exit(1)
}()
for {
fmt.Println("sleeping...")
time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee
}
}
他の回答に少し追加して、実際にSIGTERM(killコマンドによって送信されるデフォルトのシグナル)をキャッチしたい場合syscall.SIGTERM
は、os.Interruptの代わりに使用できます。syscallインターフェースはシステム固有であり、どこでも機能しない場合があることに注意してください(例:Windows)。しかし、両方をキャッチすることはうまくいきます:
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
signal.Notify
関数を使用すると、一度に複数の信号を指定できます。したがって、コードをに簡略化できますsignal.Notify(c, os.Interrupt, syscall.SIGTERM)
。
os.Kill
に対応しますsyscall.Kill
。これは、送信できるがキャッチされない信号です。コマンドと同等kill -9 <pid>
です。キャッチkill <pid>
して正常にシャットダウンする場合は、を使用する必要がありますsyscall.SIGTERM
。
上記の受け入れられた回答に(投稿の時点で)1つまたは2つの小さなタイプミスがあったので、これがクリーンアップされたバージョンです。この例では、Ctrl+ を受け取ったときにCPUプロファイラーを停止していますC。
// capture ctrl+c and stop CPU profiler
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
log.Printf("captured %v, stopping profiler and exiting..", sig)
pprof.StopCPUProfile()
os.Exit(1)
}
}()
runtime.Gosched
、適切な場所(プログラムのメインループにある場合)で呼び出す必要があります
上記のすべてがスプライスされたときに機能するように見えますが、gobyexampleの信号ページには、信号をキャプチャする非常にクリーンで完全な例があります。このリストに追加する価値があります。
死は、チャネルと待機グループを使用してシャットダウン信号を待機する単純なライブラリです。シグナルが受信されると、クリーンアップするすべての構造体でcloseメソッドを呼び出します。
syscall.SIGINTおよびsyscall.SIGTERMシグナルを検出し、signal.Notifyを使用してそれらをチャネルに中継する別のgoroutineを使用できます。チャネルを使用してそのゴルーチンにフックを送信し、関数スライスに保存できます。シャットダウン信号がチャネルで検出されると、それらの機能をスライスで実行できます。これは、リソースのクリーンアップ、実行中のゴルーチンの終了の待機、データの永続化、または部分的な実行合計の印刷に使用できます。
シャットダウン時にフックを追加して実行するための小さくてシンプルなユーティリティを作成しました。お役に立てれば幸いです。
https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go
これは「延期」して行うことができます。
サーバーを正常にシャットダウンする例:
srv := &http.Server{}
go_shutdown_hook.ADD(func() {
log.Println("shutting down server")
srv.Shutdown(nil)
log.Println("shutting down server-done")
})
l, err := net.Listen("tcp", ":3090")
log.Println(srv.Serve(l))
go_shutdown_hook.Wait()
これは、クリーンアップするタスクがある場合に機能する別のバージョンです。コードはメソッドにクリーンアッププロセスを残します。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
_,done1:=doSomething1()
_,done2:=doSomething2()
//do main thread
println("wait for finish")
<-done1
<-done2
fmt.Print("clean up done, can exit safely")
}
func doSomething1() (error, chan bool) {
//do something
done:=make(chan bool)
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
//cleanup of something1
done<-true
}()
return nil,done
}
func doSomething2() (error, chan bool) {
//do something
done:=make(chan bool)
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
//cleanup of something2
done<-true
}()
return nil,done
}
メイン関数をクリーンアップする必要がある場合は、go func()を使用してメインスレッドで信号をキャプチャする必要があります。
誰かがWindowsで信号を処理する方法を必要とする場合の記録のためだけに。os / execを介してprog Aからprog Bを呼び出すことを処理する必要がありましたが、exを介して信号を送信するため、prog Bは正常に終了できませんでした。cmd.Process.Signal(syscall.SIGTERM)またはその他のシグナルは、Windowsではサポートされていません。私が扱った方法は、一時ファイルをシグナルexとして作成することです。prog Aとprog Bを介した.signal.termは、そのファイルが間隔ベースに存在するかどうかを確認する必要があります。ファイルが存在する場合、プログラムを終了し、必要に応じてクリーンアップを処理します。他の方法があると確信していますが、これでうまくいきました。