context【Go】
処理のキャンセル
時間がかかる処理を持つgoroutine関数に対して
時間制限を持たせる場合などに引数として渡します。
func longProcess( ctx context.Context, c chan string) { // 明示的にすべきとの観点から、contextは第1引数に渡すべきである、とgodocにも記載されている。 fmt.Println("run") time.Sleep(time.Second * 2) // 2秒間待つ fmt.Println("finish") c <- "result" } func main() { c := make(chan string) ctx := context.Background() // contextを作成 ctx, cancel := context.WithTimeout(ctx, 1*time.Second) // 渡し先の処理開始から1秒間でタイムアウトさせる defer cancel() go longProcess(ctx, c) // contextを渡す loop: for { select { case <-c: fmt.Println("success") break loop case <-ctx.Done(): // contextが終了した場合 fmt.Println(ctx.Err()) // contextが終了した理由を出力 break loop } } fmt.Println("##################") }
context.WithTimeout()は、引数に生成したcontextと、timeoutに至るまでの時間を指定します。
ここで言う時間は、渡す先のgoroutine関数の処理が開始されてからの時間です。
時間に依存せず、何らかの条件分岐を使って処理を終了させたい場合には、
context.WithTimeout()の2番目の返り値である、cancel()を実行します。
(cancelは変数に格納するので、名前は任意です。)
cancelまでの時間を指定する代わりに、時刻を指定することもできます。
ctx := context.Background() ctx, cancel := context.WithDeadline(ctx, time.Now().Add(1*time.Second)) // 現在時刻から1秒後にタイムアウト defer cancel()
また、context.WithTimeout()を利用する場合、あらかじめどういう時に処理を終了させるかの条件が決まっているかと思います。
その条件が決まっていない時には、context.TODO()を用いることで、とりあえず関数やメソッドに引数を渡す、ということができます。
func main() { c := make(chan string) ctx := context.TODO() go longProcess(c, ctx) ... }
たとえ渡し先の関数やメソッドが、nil値を許容していても、nil値を渡すことは推奨されていません。
context.TODO()を使います。
ただし、後ほど紹介する、値の引き渡しも利用しない場合です。
なお、同じcontextを複数の異なるgoroutineに渡しても問題ありません。
リクエストスコープにおける値の引き継ぎ
リクエスト毎に値を保持するユーザーIDなどの引き継ぎに使用します。
//値の設定 func WithValue(parent Context, key, val interface{}) Context //値の取得 func Value(key interface{}) interface{}
keyに指定する型は、他packageとの競合を避けるため、
stringなどの標準型ではなく、type定義した型である必要があります。
また、Valueの返り値の型はinterface{}であるため、type assertionが必要です。
Valueは、値が存在するかをbool値で返すことも可能です。
以下に簡単な使用例を示します。
user_idをmain関数でセットして、goroutineで出力する、というものです。
func longProcess(ctx context.Context, c chan string) { fmt.Println("run") time.Sleep(time.Second * 2) v, ok := GetValueS(ctx, key("user_id")) // bool値も返す if ok { fmt.Println(v) } else { fmt.Println("I couldn't find user_id") } fmt.Println("finish") c <- "result" } type key string func SetValueS(ctx context.Context, key interface{}, value string) context.Context { return context.WithValue(ctx, key, value) } func GetValueS(ctx context.Context, key interface{}) (string, bool) { v, ok := ctx.Value(key).(string) // interface{}を返すため、type asertionが必要 return v, ok } func main() { c := make(chan string) ctx := context.Background() ctx = SetValueS(ctx, key("user_id"), "xch-A1281") ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() go longProcess(ctx, c) loop: for { select { case <-ctx.Done(): fmt.Println(ctx.Err()) break loop case <-c: fmt.Println("success") break loop } } fmt.Println("##################") }
値の引き渡しには以下の慣習があります。
一見便利な機能ですが、interface{}を引数とすることから、型安全でなくなる点があり、
通常の関数には用いないなど、利用には慎重になる必要があります。