概要
例えば処理が長い関数がある場合にその処理を Ctrl+c などで終了したいケースはあると思います
そんな場合に Context と Signal を使えば実現することができます
環境
- macOS 10.14.2
- golang 1.11.2
サンプルコード
少し長めです
分かりやすいようにコメントを入れています
ポイントはシグナルを go routine 内の select 文でハンドリングする点とその際に Context を cancel()
することで関数を終了させる点かなと思います
詳細は以下サンプルコードとコメントを見てもらえればと思います
package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
// 長くなるであろう任意の処理、強制終了された場合はエラーを返す
func robot(ctx context.Context) error {
count := 1
for {
if count == 3 {
// 3 回ループしたら正常終了
fmt.Println("robot end")
return nil
}
select {
case <-ctx.Done():
// case <-c: の cancel がコールされたタイミングでここがコールされる
fmt.Println("force robot end")
return errors.New("error")
default:
fmt.Println(count)
time.Sleep(1 * time.Second)
count++
}
}
}
// case <-ctx.Done(): が実行されているか確認するためのデバッグメソッド
func debug() {
fmt.Println("debug")
}
// メイン
func main() {
// バックグランドコンテキスト作成
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
// 受信したシグナルを管理するためのチャネル
c := make(chan os.Signal, 1)
// ハンドリングするシグナルの種類を登録する、os.Interrupt は Windows 用の Ctrl+c
signal.Notify(c,
syscall.SIGINT,
os.Interrupt)
// main() 終了時に必ずコールされる
defer func() {
// シグナルの受付を終了する
signal.Stop(c)
fmt.Println("defer")
}()
// ここの goroutine でシグナルのハンドリングとコンテキストの終了を待機する
go func() {
select {
case <-c:
// シグナルをキャッチ
fmt.Println("signal")
// robot() を終了させる
cancel()
case <-ctx.Done():
// cancel() コール時に呼ばれる、ただしシグナルをキャッチした際の cancel() ではここは通らない
debug()
fmt.Println("ctx.Done()")
}
}()
ret := robot(ctx)
// エラーじゃない場合は context 終了させる
if ret == nil {
cancel()
}
}
0 件のコメント:
コメントを投稿