Contextの使い方(Background, WithValue, WithCancel, WithTimeout)

goのContextの使い方を確認します。

「コンテキストの生成方法」「コンテキストに値を設定する方法」「コンテキストのキャンセル・タイムアウト」などについて取り上げます。

Contextとは

Contextを利用することにより、関数の呼び出し連鎖の中で、以下伝播を行うことができます。

  • 値の伝播
  • キャンセルの伝播
  • タイムアウトの伝播

活用シーンとしては、リソースの無駄遣いを防止したとき、キャンセル・タイムアウトの伝番を活用できます。
また、APIリクエストに関する情報を各関数で利用したいとき、値の伝播を活用できます。

空のContextの生成

context.Background() で空のContextを生成できます。

「どのContextを利用すればよいかまだ決まってない場合」「Contextをまだ利用できない場合」には、context.TODO() の利用が推奨されています。

package main

import (
	"context"
	"fmt"
)

func main() {
	ctx1 := context.Background()
	ctx2 := context.TODO()

	fmt.Printf("%+v\n", ctx1) // context.Background
	fmt.Printf("%+v\n", ctx2) // context.TODO
}

値の伝播
( WithValue, Value )

WithValueメソッド を利用すると、Key-Value形式で値がセットされた新しいコンテキストを取得できます。

セットした値は Valueメソッド で取得できます。

package main

import (
	"context"
	"fmt"
)

const (
	key1 = "wakuwaku"
	key2 = "bank"
)

func fn1(ctx context.Context) {
	ctx = context.WithValue(ctx, key1, "fn1で値をセット")
	fmt.Printf("[fn1]\tctx:%v\twakuwaku:%v\tbank:%v\n", &ctx, ctx.Value(key1), ctx.Value(key2))
	fn2(ctx)
}

func fn2(ctx context.Context) {
	ctx = context.WithValue(ctx, key2, "fn2で値をセット")
	fmt.Printf("[fn2]\tctx:%v\twakuwaku:%v\tbank:%v\n", &ctx, ctx.Value(key1), ctx.Value(key2))
}

func fn3(ctx context.Context) {
	ctx = context.WithValue(ctx, key1, "fn3で値をセット")
	fmt.Printf("[fn3]\tctx:%v\twakuwaku:%v\tbank:%v\n", &ctx, ctx.Value(key1), ctx.Value(key2))
	fn4(ctx)
}

func fn4(ctx context.Context) {
	ctx = context.WithValue(ctx, key1, "fn4で値をセット")
	fmt.Printf("[fn4]\tctx:%v\twakuwaku:%v\tbank:%v\n", &ctx, ctx.Value(key1), ctx.Value(key2))
}

func main() {
	ctx := context.Background()
	fmt.Printf("[main]\tctx:%v\twakuwaku:%v\tbank:%v\n", &ctx, ctx.Value(key1), ctx.Value(key2))
	fn1(ctx)
	fn3(ctx)
}

以下の実行結果にて、引数で指定されたコンテキストがベースとなっていることがわかります。

[main]  ctx:0x14000104220       wakuwaku:<nil>  bank:<nil>
[fn1]   ctx:0x14000104230       wakuwaku:fn1で値をセット        bank:<nil>
[fn2]   ctx:0x14000104240       wakuwaku:fn1で値をセット        bank:fn2で値をセット
[fn3]   ctx:0x14000104250       wakuwaku:fn3で値をセット        bank:<nil>
[fn4]   ctx:0x14000104260       wakuwaku:fn4で値をセット        bank:<nil>

すでに同じキーで値がセットされていれば、値が上書きされます。

キャンセルの伝播
( WithCancel )

WithCancelメソッド を利用すると、新しいコンテキストとともに キャンセル関数 を取得できます。

<-ctx.Done() でキャンセル関数の実行を検知できます。

package main

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

func fn1(ctx context.Context) {
	log("start fn1")
	defer log("done fn1")
	for i := 1; i <= 4; i++ {
		select {
		case <-ctx.Done():
			return
		default:
			log("loop fn1")
			time.Sleep(1 * time.Second)
		}
	}
}

func fn2(ctx context.Context) {
	log("start fn2")
	defer log("done fn2")
	for i := 1; i <= 4; i++ {
		select {
		case <-ctx.Done():
			return
		default:
			log("loop fn2")
		}
	}
}

func log(timing string) {
	fmt.Printf("%s second:%v\n", timing, time.Now().Second())
}

func main() {
	log("start main")
	defer log("done main")
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)

	go fn1(ctx)
	go fn2(ctx)
	time.Sleep(2 * time.Second)
	cancel()
	time.Sleep(2 * time.Second)
}

fn1関数 のほうはループが全て完了する前にキャンセルが実行されたため、2回しかループ処理が実行されずに終了されました。

start main second:37
start fn2 second:37
loop fn2 second:37
loop fn2 second:37
loop fn2 second:37
loop fn2 second:37
done fn2 second:37
start fn1 second:37
loop fn1 second:37
loop fn1 second:38
done fn1 second:39
done main second:41

タイムアウトの伝播
( WithTimeout )

WithTimeoutメソッド を利用すると、タイムアウトを設定したコンテキストを取得できます。

以下例では、2秒でタイムアウトするコンテキストを生成しています。

package main

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

func fn1(ctx context.Context) {
	log("start fn1")
	defer log("done fn1")
	for i := 1; i <= 4; i++ {
		select {
		case <-ctx.Done():
			return
		default:
			log("loop fn1")
			time.Sleep(1 * time.Second)
		}
	}
}

func fn2(ctx context.Context) {
	log("start fn2")
	defer log("done fn2")
	for i := 1; i <= 4; i++ {
		select {
		case <-ctx.Done():
			return
		default:
			log("loop fn2")
		}
	}
}

func log(timing string) {
	fmt.Printf("%s second:%v\n", timing, time.Now().Second())
}

func main() {
	log("start main")
	defer log("done main")
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	go fn1(ctx)
	go fn2(ctx)
	time.Sleep(5 * time.Second)
}

fn1関数 のほうはループが全て完了する前にタイムアウトになったため、2回しかループ処理が実行されずに終了されました。

start main second:1
start fn2 second:1
loop fn2 second:1
loop fn2 second:1
loop fn2 second:1
loop fn2 second:1
done fn2 second:1
start fn1 second:1
loop fn1 second:1
loop fn1 second:2
done fn1 second:3
done main second:6

参考

【エンジニア向け】仕事を見つける方法

転職する

転職エージェントを活用する

転職サイトの場合、自身でサイト上から企業を探す必要があります。 一方「レバテックキャリア」 などの転職エージェントの場合、エージェントが企業を紹介してくれます。エージェントが間に入ることにより、日程調整や、条件交渉などもサポートしてくれます。

転職ドラフトを活用する

転職ドラフト」は、 企業がITエンジニアをドラフトという形で指名するサービスです。年収が最初に提示されるなどのメリットがあります。 ただ、初回登録時にレジュメ作成が必要で、すでにエンジニア経験が豊富にあるエンジニア向けのサービスかと思います。 レジュメ作成が手間ですが、自身のキャリアを見直す機会になり、他の仕事探しにも役立つはずです。

エンジニア転職保証のあるスクールを活用する

ある程度、開発経験のあるかたであれば、独学で必要なスキルを身につけることができるはずです。ただ、別業種からエンジニアに転職したい場合など、1から独学で学ぶのはハードルが高いです。そういった方は、スクールの活用を検討しても良いと思います。 「TechAcademy」は、エンジニア転職保証コースを提供しています。給付金制度の対象講座として認定されているため、金銭面の負担も抑えることができます。

フリーランスとして活動する

レバテックフリーランス」「ITプロパートナーズ」「ギークスジョブ」は、フリーランスエージェントサービスです。 エージェントによって、支払いサイトなど細かい違いはありますが、まずは良い案件を見つけることが重要です。 登録自体は無料なので、複数エージェントに登録して、より多くの案件を紹介してもらうのがおすすめです。

logo
わくわくBank.
技術系の記事を中心に、役に立つと思ったこと、整理したい情報などを掲載しています。