概要
golang の context パッケージ の基本をサンプルを交えて学びました
環境
- macOS 10.14.1
- golang 1.11.2
基本
context.Background()
でメインのコンテキストを作成します
そしてそこから以下 4 つのコンテキストのどれかを作成することでタイムアウト処理やキャンセル処理を実装します
context.WithCancel(ctx)
context.WithTimeout(ctx)
context.WithDeadline(ctx)
context.WithValue(ctx)
それぞれサンプルを交えて紹介します
WithCancel
cancel()
関数をコールすることで goroutine 側の処理を強制終了させることができます
package main
import (
"fmt"
"context"
"time"
)
func worker(ctx context.Context) {
for i := 0; i < 10; i++ {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
}
func main() {
mainctx := context.Background()
subctx, cancel := context.WithCancel(mainctx)
go worker(subctx)
time.Sleep(5 * time.Second)
cancel()
select {
case <-subctx.Done():
fmt.Println("done")
}
}
worker 側は 0 から 9 までの数字を 1 秒おきに表示します
それを WithCancel を使うことで 0 から 4 まで表示させて終了させるサンプルです
WithCancel は新たなコンテキストと goroutine 側の処理をキャンセルするための cancel()
関数を返します
cancel()
をコールすることで context.Context
型の引数を持つ goroutine 側の処理を強制終了させることができます
また cancel()
がコールされると Context.Done()
に終了したメッセージを送信します
Context.Done()
はチャネルになっています
なので select と組み合わせてメイン側で使うことで goroutine 側の処理を待ちます
チャネルなので cancel()
からメッセージが送信されていない状態で Context.Done()
を参照しようとすると deadlock エラーになります (default を使ってノンブロッキングにすることもできます)
なので cancel()
関数は必ず呼ばれることが想定されています
今回のサンプルはスリープ -> cancel の流れになっていますが本来はあるアクションやある条件になったら cancel をコールして強制終了させるという使い方になるかなと思います
WithTimeout
指定時間経過したら強制終了させるコンテキストです
package main
import (
"fmt"
"context"
"time"
)
func worker(ctx context.Context) {
for i := 0; i < 10; i++ {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
}
func main() {
mainctx := context.Background()
subctx, cancel := context.WithTimeout(mainctx, 5 * time.Second)
defer cancel()
go worker(subctx)
select {
case <-subctx.Done():
fmt.Println("done")
}
}
サンプルとしては先程の WithCancel と同じ動きになります
こちらは指定時間後にキャンセルさせるためのコンテキストになります
cancel()
は defer を使って確実にコールするようにします
いわゆるタイマーのような動作をします
タイマーが 0 になったら終了させたい場合に使えるかなと思います
WithDeadline
指定の時刻に処理を終了させることができます
package main
import (
"fmt"
"context"
"time"
)
func worker(ctx context.Context) {
for i := 0; i < 10; i++ {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
}
func main() {
mainctx := context.Background()
subctx, cancel := context.WithDeadline(mainctx, time.Now().Add(5 * time.Second))
defer cancel()
go worker(subctx)
select {
case <-subctx.Done():
fmt.Println("done")
}
}
先程の WithTimeout
は引数に time.Duration
を取ります
WithDeadline
は時刻を指定することができる time.Time
を引数に取ります
サンプルは現在時刻から 5 秒後時間で cancel()
するコンテキストを生成しています
時刻を扱う処理になるのでタイムゾーンには注意しましょう
WithValue
key/value として使えるコンテキストです
goroutine の強制終了には使えないので他と組み合わせる必要があります
package main
import (
"fmt"
"context"
"time"
)
func worker(ctx context.Context) {
for i := 0; i < 10; i++ {
msg :="%s:%d"
fmt.Println(fmt.Sprintf(msg, ctx.Value("key"), i))
time.Sleep(1 * time.Second)
}
}
func main() {
mainctx := context.Background()
subctx := context.WithValue(mainctx, "key", "value")
subctx2, cancel := context.WithTimeout(mainctx, 5 * time.Second)
defer cancel()
go worker(subctx)
select {
case <-subctx2.Done():
fmt.Println("done")
}
}
コンテキストには伝播という仕組みもあるので goroutine 側で新たにコンテキストを作成して goroutine ないで cancel しても良いと思います
最後に
golang のコンテキストの基本的な使い方を学んでみました
サンプルを動かすことで理解も深まると思います
実際は複数のコンテキストを組み合わせて使ったり伝播したりするのでもっと複雑になると思いますが基本は 4 つなので覚えておきましょう
0 件のコメント:
コメントを投稿