Go の context
Go を書くときに何となく書いてしまっている context
を運用可能に持っていく。
お題
Context を使って、リクエストを投げている時に、一定の時間がたったらグレースフルにリクエストを停止するプログラムを書く。リクエストを処理する関数の方では、Context の Value を取得すること
上記のお題をさっと書けるように、いろいろ調べてみよう。
Context とは
Go Lang のコンテキストは、デッドライン、キャンセレーションシグナル、他のリクエストスコープのバリューを伝播させるものである。 C# とかでいうところの、CancellationToken に近い雰囲気を持っている。これを理解していこう。
Context の生成
コンテキストは、さまざまなAPIで渡す必要があり、よく理解せずに渡しているケースもあるだろう。主に2つのケースがある。
ctx := context.Background()
ctx := context.TDDO()
それぞれ、どう違うだろうか?説明を読むと、どちらも空の、Non-nil のコンテキストなので、違いは無いように思うが、普通は、Background()
を使う。なぜなら、TODO()
は、どういうコンテキストを使うかわからない場合に使う、つまりその名の通りTODOなのだw
Context をキャンセル可能にする
Context をキャンセル可能にしたい場合は次のように書く。WithCancel
の戻りは、コンテキストと、context.CancelFunc
つまり関数である。帰ってきた関数を実行すると、コンテキストがキャンセルされる。他にもWithDeadline
等があり、ある時間になったら自動的にキャンセルされるものも書くことができる。
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(5 * time.Second)
cancel()
}()
Cancel をフックする
キャンセルがかかったとすると、どうやって、そのキャンセルをフックすればいいのだろうか?コンテキストを渡される側の関数のサンプルを書いてみたい。コンテキストのDone()
関数は、コンテキストがキャンセル、もしくは、デッドラインに到達したときに、チャネルの型になっているので、チャネルに送信される。下記のようなプログラムで、他のチャネルに正常終了のチャネルを持たせて、待ち受けるとかすると良さげ。実際のライブラリがどう書かれているのかも読んでパターンを学びたい。
func processRequest(ctx context.Context, uri string) bool {
finished := make(chan bool)
go func() {
// Do something.
time.Sleep(10 * time.Second)
finished <- true
}()
select {
case <-ctx.Done():
fmt.Println("canceled!")
return true
case <-finished:
fmt.Println("successfully done")
return false
}
}
Context に値を渡す
他に、context
には値を持たすことが出来る。ただ、context.Background
は、値を持たせることが出来ないので、次のように書く。
ctx := context.WithValue(context.Background(), "key", "value")
fmt.Printf("Value: %s\n", ctx.Value("key")
実は上記は正しく実行されるが、推奨されない書き方で、GoLint で警告される。本来のおすすめの書き方はこうなる。
type key string
const(
sessionID key = "sessionID"
)
:
ctx :context.WithValue(context.Background(), sessionID, "value")
fmt.Printf("Value: %s\n", ctx.Value(sessionID)
なぜこう書かないといけないか?というと、パッケージ感での、key の衝突を避けるためである。GoLintの警告を読むと、KeyはStringなどのプリミティブ型では、衝突が起こるので使うべきでないとある。例えば、"key" というものを使っているとすると、伝播してつかわれる context なので、どこかのパッケージで同じ"key" を使っているかもしれないからである。これを避けるために、type key string
を定義することによって、ネームスペースによって明確に別の型として認識されるので、衝突がおこならないかだ。衝突を避けるために、key
や、sessionID
を小文字スタートにして、private にしている。
Context とツリー構造
では、コンテキストに、キーを渡す場合、キーを渡してかつ、キャンセルしたい場合はどうしたらよいだろうか?
ancestorCtx := context.WithValue(context.Background(), sessionID, "value1") // Key should be comparable in real world.
parentCtx := context.WithValue(ancestorCtx, sessionID, "overridden value") // key should be comparable in real world.
ctx, cancel := context.WithCancel(parentCtx)
fmt.Printf("Value : %s\n", ctx.Value(sessionID))
コンテキストの値は、引き継がれるので上記のように書ければよい。ちなみに実行すると、後勝ちで、同じKeyなら上書きされる。
$ ./main
Value : overridden value
コンテキストは、ツリー状に管理されている。Golang context.WithValue: how to add several key-value pairs
この StackOverflow の解答の一つに書いてあるのだが、コンテキストは、Keyの値をオブジェクトをさかのぼってキーが見つかるまで検索していくためだ。だから、コンテキストは、普通親コンテキストが引数として渡される必要がある。context.Background() は必ず一番の祖先になる。
解答
さて、自分なりの解答を作ってみた。
package main
import (
"context"
"fmt"
"time"
)
type key string
const (
sessionID key = "session"
)
func main() {
// context.TODO()
ancestorCtx := context.WithValue(context.Background(), sessionID, "value1") // Key should be comparable in real world.
parentCtx := context.WithValue(ancestorCtx, sessionID, "overridden value") // key should be comparable in real world.
ctx, cancel := context.WithCancel(parentCtx)
go func() {
time.Sleep(5 * time.Second)
cancel()
}()
processRequest(ctx, "http://localhost:7071/api/hello")
}
func processRequest(ctx context.Context, uri string) bool {
fmt.Printf("Received context value sessionID: %s\n", ctx.Value(sessionID))
finished := make(chan bool)
go func() {
// Do something.
time.Sleep(10 * time.Second)
finished <- true
}()
select {
case <-ctx.Done():
fmt.Println("canceled!")
return true
case <-finished:
fmt.Println("successfully done")
return false
}
}
実行結果
$ ./main
Received context value sessionID: overridden value
canceled!
Author And Source
この問題について(Go の context), 我々は、より多くの情報をここで見つけました https://qiita.com/TsuyoshiUshio@github/items/e824328c3c109a9eb766著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .