156
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go の Context を学ぶ

Last updated at Posted at 2018-05-06

Azure SDK for go を使っていると、突然 Context というものが出てきて意味がわからなかったので調べて見た。

公式のリファレンスを読んだのちに

次のブログがとてもわかりやすかった。

ざっとまとめると、Context は、APIのサーバーやクライアントを使うときに、コンテキストを提供してキャンセルや、タイムアウト、値を渡したり出来る仕組み。

Context 構造体の持っている属性や関数に関しては上記のブログに書いてあるので、ここでは書かないけど、何もなし(Background) キャンセル、タイムアウト、値渡しなどの関数が存在する。Context の構造体自体はキャンセルのメソッドは提供しないが、Context はデコレーターの様になっておりラップが可能になっている。上記のブログのサンプルを拡張して見た。

基本的な使い方

go routine などを呼び出す元の方でオブジェクトを生成する。context.Background() は空のコンテキストを生成する。

ctx := context.Background()

これをgo routine を実行時に引き渡す。

	go infiniteLoop(ctx)

go routine の方では、それを受け取る。この例ではループが回るが、キャンセルされたら、Done() が呼ばれるので、メッセージを出力する。

ポイントは、コンテキストオブジェクトは、デコレータの様な使い方をするところ。この例では、渡ってきたのは、空のContextが渡ってきているが、ここで、context.WithCancel(ctx) の様にラップしてキャンセル機能を追加している。

func infiniteLoop(ctx context.Context) {
	innerCtx, cancel := context.WithCancel(ctx)
	defer cancel()
	for {
		fmt.Println("Waiting for time out")

		select {
		case <-innerCtx.Done():
			fmt.Println("Exit now!")
			return
		default:

		}
	}
}

タイムアウトの実装

たとえばタイムアウトを追加したければ、本体側でこんな感じで書けば良い。キャンセルされたらキャンセルの関数が呼ばれる様に defer 関数を使っておく。

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
        defer cancel()

しっかりと 5秒後にキャンセルされ、Done が呼ばれる。

Key/Value 値を設定する

Context はキーバリューも保持できる様になっている。これもデコレータ的な使い方をする。

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	ctx = context.WithValue(ctx, "message", "hi")

go routine 側でこの値を取りたければこんな感じにすると良い。KeyValue の型はinterface {} なので、型変換が必要。

fmt.Println("message:", ctx.Value("message").(string))

コード全体

こちらにコードを置いておいた

全体を載せておく。

package main

import (
	"context"
	"fmt"
	"time"
)

func infiniteLoop(ctx context.Context) {
	innerCtx, cancel := context.WithCancel(ctx)
	defer cancel()
	for {
		fmt.Println("Waiting for time out")
		//time.Sleep(time.Second)
		// If I put sleep in here, fmt.Println doesn't output the "Exit now!"

		select {
		case <-innerCtx.Done():
			fmt.Println("Exit now!")
			fmt.Println("message:", ctx.Value("message").(string))
			return
		default:

		}
	}
}

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	ctx = context.WithValue(ctx, "message", "hi")
	defer cancel()

	go infiniteLoop(ctx)

	select {
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
}

次のステップ

Context は理解できたと思うが、上記のサンプルコードには一点わからないことがある。上記のもので、time.Sleep(time.Second) を有効にすると、キャンセル時に、fmt.Println("Exit now!") が実行されない。なんでだろう。

156
106
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
156
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?