time.Ticker で遊ぶ【Go 1.16 バージョン】
ずいぶん前に「time.Ticker で遊ぶ」と言う記事を書いたのだが,先日リリースされた Go 1.16 で signal
.NotifyContext()
関数が追加された記念に,これを使った改訂版の記事を書いてみたいと思う。
前回と同じくお題は以下の通り。
- 一定周期ごとの処理を行う
- Ctrl+C 等の割り込み処理を行う
一定周期ごとの処理を行う
これは前回の記事をほぼそのまま使いまわそう。
// +build run
package main
import (
"fmt"
"time"
)
func ticker() {
t := time.NewTicker(1 * time.Second) //1秒周期の ticker
defer func() {
fmt.Println("Stopping ticker...")
t.Stop()
}()
for {
select {
case now := <-t.C:
fmt.Println(now.Format(time.RFC3339))
}
}
}
func main() {
ticker()
}
前回でも説明した通り, defer 構文を使って終了時に time
.Ticker.Stop()
関数で周期イベントを止めようとしているが,実際には無限ループなので return まで到達しない(笑)
NotifyContext 関数で SIGNAL を捕まえる
Go では SIGINT や SIGTERM といった OS から送信される SIGNAL をイベントとして channel に送り込む仕掛けがある(ちなみに Ctrl+C は SIGINT として送られる)。
さらに Go 1.16 では SIGNAL イベントを context
.Context
のキャンセル・イベントとして実装できるようになった。
たとえば,こんな感じに書ける。
// +build run
package main
import (
"context"
"fmt"
"os"
"os/signal"
"time"
)
func ticker(ctx context.Context) {
t := time.NewTicker(1 * time.Second) //1秒周期の ticker
defer func() {
fmt.Println("Stopping ticker...")
t.Stop()
}()
for {
select {
case now := <-t.C:
fmt.Println(now.Format(time.RFC3339))
case <-ctx.Done():
fmt.Println("cancellation from context:", ctx.Err())
return
}
}
}
func run() {
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
ticker(ctx)
}
func main() {
run()
}
context
パッケージは並行処理下で使うことが多いだろう。
たとえば run()
関数をこんな感じに書き換えてみるか。
func run() {
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
n := i + 1
wg.Add(1)
go func() {
defer wg.Done()
ticker(ctx, n)
}()
}
wg.Wait()
}
これで平行に動作している全ての ticker()
に対してキャンセルを送り込むことができる。
上のコード例ではひとつの context
.Context
インスタンスを複数の goroutine で使いまわしているが,以下のように
func run() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
n := i + 1
wg.Add(1)
go func() {
defer wg.Done()
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
ticker(ctx, n)
}()
}
wg.Wait()
}
各 goroutine ごとに context
.Context
インスタンスを生成してセットしても全ての ticker()
を Ctrl+C
で問題なく止めることができた。
キャンセル・イベントの伝搬
context
パッケージは,名前の通り,異なるレイヤやドメイン間でコンテキスト情報を受け渡しするためのパッケージだが,親から子にキャンセルイベントが伝搬する性質がある(逆向きには伝搬しない)。
たとえば
func run() {
parent, _ := signal.NotifyContext(context.Background(), os.Interrupt)
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
n := i + 1
wg.Add(1)
go func() {
defer wg.Done()
child, _ := context.WithTimeout(parent, time.Duration(n)*5*time.Second)
ticker(child, n)
}()
}
wg.Wait()
}
などとすれば各 goroutine の ticker()
関数に SIGNAL イベントとタイムアウト・イベントの両方を仕込むことができる。
また
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
とした場合の返り値の cancel
は関数値になっていて,これをキックすることでペアとなっている context
.Context
インスタンス(上のコードなら ctx
)にキャンセル・イベントを発生させることができる。
実際の使い方として signal
.NotifyContext()
関数は main goroutine に近いところで context
.WithCancel()
関数と置き換えることが多いのではないだろうか。
context
について詳しくは『Go 言語による並行処理』の 4.12 章が参考になる。
素敵なキャンセル・ライフを(笑)
ブックマーク
参考図書
- Go言語による並行処理
- Katherine Cox-Buday (著), 山口 能迪 (翻訳)
- オライリージャパン 2018-10-26
- 単行本(ソフトカバー)
- 4873118468 (ASIN), 9784873118468 (EAN), 4873118468 (ISBN)
- 評価
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。