お題
気まぐれでgoroutineと戯れる。。。
関連記事Index
開発環境
# OS
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.1 LTS (Bionic Beaver)"
# 言語
$ go version
go version go1.11.4 linux/amd64
実践
OSシグナルを受け付けて複数goroutineを停止
複数のgoroutineを生成実行し、メインgoroutineはOSシグナルを待ち受ける。
OSシグナルから受信があると、実行中の複数goroutineすべてに停止通知を行う。
■OSシグナルの受け手
OSシグナルを受信したら、あらかじめ受け取っておいたgoroutine停止用のキャンセル通知関数notifyToStop
を実行する。
これにより、事前に実行していた複数goroutineに”停止”を通知する。
受け取るOSシグナルはSIGINT
だけ。(とりあえずCtrl+Cでキーボード割り込みした事例で良いので)
type signalReceiver struct {
ch chan os.Signal // OSからのシグナル受信用チャネル
notifyToStop func() // OSからのシグナル受信時の他チャネルへの停止通知関数
}
func newSignalReceiver(notifyToStop func()) *signalReceiver {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT)
return &signalReceiver{
ch: ch,
notifyToStop: notifyToStop,
}
}
func (r *signalReceiver) wait() {
<-r.ch
fmt.Println("received signal !!!!!")
r.notifyToStop()
}
■goroutine実行対象の関数
受け取ったname
を1秒おきに出力し続けるだけの関数。
コンテキストから終了通知を受け取ったら、処理終了。
// 1秒おきにnameを出力
func echoLangName(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println("***** stop echo " + strings.TrimSpace(name) + " *****")
return
default:
fmt.Println(name)
time.Sleep(1 * time.Second)
continue
}
}
}
■main関数
5言語(選択は適当)分のgoroutineを生成したら、あとはメインgoroutineでOSシグナル受信待ち。
func main() {
// エコー対象の言語は5つ
langs := []string{
"Go",
" Java",
" C++",
" PHP",
" Ruby"}
ctx, cancelFunc := context.WithCancel(context.Background())
for _, lang := range langs {
go echoLangName(ctx, lang)
}
rec := newSignalReceiver(cancelFunc)
rec.wait()
time.Sleep(3 * time.Second) // 本来はWaitGroup使う
}
実行
開始から2秒くらい経ったところで Ctrl+C
で割り込み。
すると、OSシグナルからの割り込みを待っていた wait()
関数にて「received signal !!!!!
」を出力。
その後、あらかじめ用意しておいたキャンセル通知関数の実行により、main以外の各goroutineが停止。
参考
今回載せた動画GIFは↓を参考に作ってみました。使い勝手もいいし、できたGIFも軽い。
https://qiita.com/syohex/items/d7076828c54114eb7657