使わないから全然覚えられない.
とりあえずサンプルを書いて覚えておく.
詳細や思想はdeeeetさんの記事が非常にわかりやすいのでこれ読めばそれでいいと思う.
大雑把な理解
- context.Backgroundでcontextを作るか, よそからもらってきたcontextを使って
- With****メソッドを使ってcontextに意味を持たせる
- 結果や値はcontextオブジェクトの中に入ってる
メソッド
With****メソッドは以下がある.
メソッド名 | 用途 |
---|---|
WithCancel | キャンセル可能にする |
WithTimeout | 一定時間でキャンセルされるようにする |
WithDeadline | 指定時間でキャンセルされるようにする |
WithValue | 値を一緒に引き渡す |
どれも新しいcontextオブジェクトを返してくれる.
メンバ
contextのインターフェースは以下のメンバでとてもシンプル. コメントで書いてる意味がホントに正しいかはちょっと怪しいけど.
type Context interface { // デッドライン時刻とデッドラインが設定されているかどうか(falseなら未設定) Deadline() (deadline time.Time, ok bool) // 完了/キャンセルを知らせるチャネル Done() <-chan struct{} // エラー Err() error // WithValueで持ち運ぶ値 Value(key interface{}) interface{} }
キャンセル可能にする
キャンセル可能にするにはWithCancelを使います.
以下は無限ループで助けを呼び続けるゴルーチンを2秒後にキャンセルする例です.
WithCancelで返される2つ目の関数を呼び出せばキャンセルされるんですね.
package main import ( "context" "fmt" "time" ) func infiniteLoop(ctx context.Context) { // 終わらないやつ for { fmt.Println("Help!") } } func main() { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) go infiniteLoop(ctx) // 2秒待ってキャンセルする time.Sleep(2 * time.Second) cancel() select { case <- ctx.Done(): fmt.Println(ctx.Err()) } }
[実行結果]
Help! Help! Help! . . . Help! Help! context canceled Help! Help! Help! Help!
プロセスが死ぬまでゴルーチンが生きてるので, ずっと助けてと叫んでますが....
指定時間経過したらキャンセルする
上記の例だとWithTimeoutを使ってもっとシンプルに書くことができます.
package main import ( "context" "fmt" "time" ) func infiniteLoop(ctx context.Context) { // 終わらないやつ for { fmt.Println("Help!") } } func main() { ctx := context.Background() // 2秒待ってキャンセルする ctx, cancel := context.WithTimeout(ctx, 2 * time.Second) defer cancel() go infiniteLoop(ctx) select { case <- ctx.Done(): fmt.Println(ctx.Err()) } }
[実行結果]
Help! Help! Help! Help! . . . Help! Help! Help! context deadline exceeded Help! Help! Help! Help! Help! Help! Help!
指定時間になったらキャンセルする
相対的な時刻指定だけではなく, 絶対的な時刻指定も行う事ができます.
package main import ( "context" "fmt" "time" ) func infiniteLoop(ctx context.Context) { // 終わらないやつ for { fmt.Println("Help!") } } func main() { ctx := context.Background() // 2秒後をデッドラインにする ctx, cancel := context.WithDeadline(ctx, time.Now().Add(2 * time.Second)) defer cancel() go infiniteLoop(ctx) select { case <- ctx.Done(): fmt.Println(ctx.Err()) } }
[実行結果]
Help! Help! Help! Help! . . . Help! Help! Help! Help! context deadline exceeded Help!
時間設定したけどやっぱり手動でキャンセルする
WithTimeoutやWithDeadlineでキャンセル時刻を指定していても, 戻り値のcancel関数を呼び出せば好きなタイミングでキャンセルを行えます.
package main import ( "context" "fmt" "time" ) func infiniteLoop(ctx context.Context) { // 終わらないやつ for { fmt.Println("Help!") } } func main() { ctx := context.Background() // 2秒後をデッドラインにする ctx, cancel := context.WithDeadline(ctx, time.Now().Add(2 * time.Second)) defer cancel() // やっぱすぐやめた cancel() go infiniteLoop(ctx) select { case <- ctx.Done(): fmt.Println(ctx.Err()) } }
[実行結果]
$ ./context.exe Help! context canceled Help! Help! Help! Help!
キャンセルを伝搬させる
流石にプロセス死ぬまで叫び続けるのはかわいそうなので, キャンセル時には脱出させてあげます.
contextを作成する際にcontextを渡してあげると, Done()を経由してcancelが伝搬していきます.
package main import ( "context" "fmt" "time" ) func infiniteLoop(ctx context.Context) { innerCtx, cancel := context.WithCancel(ctx) defer cancel() // 終わらないやつ だったのを終わるようにした for { fmt.Println("Help!") select { case <- innerCtx.Done(): fmt.Println("Exit from hell.") return } } } func main() { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) // 無限ループに入る go infiniteLoop(ctx) // やっぱすぐやめた cancel() select { case <- ctx.Done(): fmt.Println(ctx.Err()) } // infiniteLoopのゴルーチンにキャンセルする余裕をあげる // これなしだとゴルーチンへ遷移する前にメインルーチンが終了しちゃう様なので time.Sleep(1 * time.Second) }
[実行結果]
$ ./context.exe Help! Exit from hell. context canceled
地獄から出てこれました.よかったですね.
値を引き渡す
contextを使って値を渡していく時にはcontext.WithValueを使います.
値を取得するにはcontext.Valueにキーを渡して取得します.
(interface型が返るので, 型アサーションがいりますね)
package main import ( "context" "fmt" ) func main() { ctx := context.Background() ctx = context.WithValue(ctx, "hoge", 1) fmt.Println(ctx.Value("hoge").(int)) }
[実行結果]
$ ./context.exe 1
追記
deeeetさんのeが一つ多かったので訂正しました.(ごめんなさい)
@niconegotoさんありがとうございました.
「キャンセルを伝搬させる」の項のサンプルコードを一部修正しました.
(go 1.10.1で確認)
id:fjwr38 さんありがとうございました.