Go1.7で golang.org/x/net/context が
標準ライブラリに入るようなので改めて使い方のメモ。
変数の管理
ctx := context.Background()
ctx = context.WithValue(ctx, "Foo", 1)
ctx = context.WithValue(ctx, "Bar", 2)
fmt.Println(ctx.Value("Foo").(int)) // 1
よくあるやつ。値を代入するたびに新しいcontextが生成される。
なので、乱暴に値を上書かない限りはあまり状態を気にしなくていい。
TimeoutとCancel
func WithCancel
func WithDeadline
func WithTimeout
はキャンセルを取り扱う。
WithTimeout
, WithDeadline
は特定時間になったタイミングでキャンセルされる。
引数で指定の仕方が 何秒後かと時刻指定がなのが違うだけ。
実際にWithTimeout
の中身は WithDeadlineを呼んでいるだけ。
WithTimeout, WithDeadline
指定した時間後にキャンセルイベントが起きる。
// 5秒かかる処理
func heavyFunc(ctx context.Context) {
time.Sleep(5 * time.Second)
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second) // 1秒後にキャンセル
defer cancel()
go heavyFunc(ctx)
go heavyFunc(ctx)
select {
case <-ctx.Done(): // 1秒以上かかると Done() がcloseされる
fmt.Println("done:", ctx.Err()) // done: context deadline exceeded
}
}
WithCancel
自分でキャンセルを使う場合に使う
func heavyFunc(ctx context.Context) {
time.Sleep(5 * time.Second)
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
go heavyFunc(ctx)
go heavyFunc(ctx)
cancel() // 即キャンセル
select {
case <-ctx.Done():
fmt.Println("done:", ctx.Err()) // done: context canceled
}
}
キャンセルの伝搬
親がキャンセルされると子にキャンセルが伝搬されていく
func main() {
ctx := context.Background()
parent, parentCancel := context.WithCancel(ctx)
child, childCancel := context.WithCancel(parent)
defer childCancel()
go heavyFunc(ctx)
go func() { // 親を1秒後にキャンセル
time.Sleep(time.Second)
parentCancel()
fmt.Println("parent cancelled")
}()
select {
case <-child.Done(): // 子もキャンセルされる
fmt.Println("parent:", parent.Err()) // parent: context canceled
fmt.Println("child:", child.Err()) // child: context canceled
}
}
使い方
goroutineで処理を実行して、所定より時間がかかった場合にエラーを返すのに使う。
ただ、結果が欲しい場合は、contextとは違う形でchannelを使い、値を受け取って返すようにする。(だいたいこっちの使い方になる)
func heavyFunc(ctx context.Context) error {
time.Sleep(time.Second)
return nil
}
func exec(ctx context.Context) error {
errChan := make(chan error, 1)
go func() {
errChan <- heavyFunc(ctx)
}()
select {
case <-ctx.Done(): // キャンセルが発生した場合
return ctx.Err()
case err := <-errChan: // heavyFuncの結果を取得した場合
return err
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 2*time.Second) // 全体2秒でタイムアウト
defer cancel()
if err := exec(ctx); err != nil {
log.Fatal(err)
} else {
log.Print("ok")
}
}
まとめ
contextを使うと、値の取り回しやキャンセル処理は楽になる。だけど、goroutineを大量に使って細かく状態を管理をやってくれるものではないので注意。