Help us understand the problem. What is going on with this article?

[Go]contextパッケージの解説

More than 1 year has passed since last update.

概要

Go言語ではゴルーチンによりコードを並行に実行することができ、その同期処理をchannelでハンドリングできます。
channelは値の読み書き、キャンセルをすることができますが、タイムアウトやエラーが起こったときのハンドリングするには独自に実装する必要があります。
contextパッケージを使うことでキャンセルの理由や関数処理を終わらせるデッドラインなどの追加情報も伝達できます。

この記事ではcontextパッケージについて解説します。

開発環境

$ go version
go version go1.10 darwin/amd64

はじめのサンプルコード

まず、動くサンプルコードをみてみます。チャネルを使ったコードとContextを使ったコードです。

「Go言語による並行処理」にあったものをサンプルコードを私のGitHubに載せています。

https://github.com/SatoTakeshiX/sample-go-context

チャネルのみを使う

サンプルコードはこちら

image.png

https://github.com/SatoTakeshiX/sample-go-context/blob/master/useChanal/chanel.go

contextを使う

サンプルコードはこちら

image.png

https://github.com/SatoTakeshiX/sample-go-context/blob/master/useContext/context.go

contextパッケージの定義

公式ドキュメントからcontextの定義をみてみましょう。

Variables

var Canceled = errors.New("context canceled")

CanceledContext.Errから返されるエラーであり、contextがキャンセルされた時に呼ばれます。

var DeadlineExceeded error = deadlineExceededError{}

DeadlineExceededは、コンテキストの期限が過ぎたときにContext.Errによって返されるエラーです。

func WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

返されたcancel関数が呼ばれた時にそのdoneチャネルを閉じる新しいContextを返します。
返されたコンテキストのDoneチャネルは、返されたcancel関数が呼び出されたとき、または親コンテキストのDoneチャネルが閉じられたときのどちらか早いほうで閉じられます。
このコンテキストをキャンセルすると、それに関連するリソースが解放されるため、このコンテキストで実行されている操作が完了するとすぐにコードはcancel関数を呼び出す必要があります。

func WithDeadline

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

マシンの時計が与えられたdeadlineの時刻を経過したらそのdoneチャネルを閉じる新しいContextを返します。
期限がdeadlineより遅くならないように調整された親コンテキストのコピーを返します。親の期限がすでにdeadlineよりも早い場合は意味的には親コンテキストと同じものを返します。。
返却されたコンテキストのDoneチャネルは、期限が切れる、返却されたcancel関数が呼ばれた、または親コンテキストのDoneチャネルが閉じられたどちらか早い方で閉じられます。
このコンテキストをキャンセルすると、それに関連するリソースが解放されるため、このコンテキストで実行されている操作が完了するとすぐにコードはcancelを呼び出す必要があります。

func WithTimeout

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

与えられたtimeoutだけ経過したらそのdoneチャネルを閉じる新しいContextを返します。
具体的にはWithDeadline(parent, time.Now().Add(timeout))を返します。
このコンテキストをキャンセルするとそれに関連するリソースが解放されるので、このコンテキストで実行されている操作が完了するとすぐにコードはcancelを呼び出す必要があります。

サンプルコード

この例では、タイムアウト付きのコンテキストを渡して、タイムアウトが経過した後にコンテキストをその機能を放棄するようにブロッキング関数に伝えます。

code

// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()

select {
case <-time.After(1 * time.Second):
    fmt.Println("overslept")
case <-ctx.Done():
    fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}

output

context deadline exceeded

type CancelFunc

type CancelFunc = context.CancelFunc

CancelFuncは、その作業を中止するように操作に指示します。 CancelFuncは作業が停止するのを待ちません。最初の呼び出しの後、CancelFuncへの後続の呼び出しは何もしません。

type Context

type Context = context.Context

Contextは、期限、キャンセルシグナル、およびAPIの境界を越えてその他の値を伝達します。 コンテキストのメソッドは、複数のゴルーチンによって同時に呼び出されることがあります。

func Background

func Background() Context

Backgroundはnil以外の空のContextを返します。キャンセルされることはなく、値もなく、期限もありません。通常、main関数、初期化、テストによって使用され、着信要求の最上位コンテキストとして使用されます。
Contextを始める際に使用するメソッドです。

func TODO

func TODO() Context

TODOはnil以外の空のContextを返します。
本番環境で使うことをは想定していないが、どのContextを使ったらいいかが不明なとき、上流のコードの実装が終わっていないが何かしらのContextが来ることはわかっているときのプレースホルダーを提供する。
TODOは、コンテキストがプログラム内で正しく伝播されるかどうかを判断する静的分析ツールによって認識されます。

func WithValue

func WithValue(parent Context, key interface{}, val interface{}) Context

WithValueは、keyに関連付けられた値がvalである新しいContextを返します。
コンテキスト値はプロセスやAPIの境界を通過するリクエストスコープでのデータに絞って使いましょう。
関数にオプションのパラメーターを渡すために使うべきではありません。

WithValueメソッドを使う際に必要なことは次の2つです。

  • 使用するキーはGoでの比較可能性を満たさなければなりません。つまり、等値演算子の==と!=を使った時に正しい値を返す必要があります。
  • 返された値は複数のゴルーチンからアクセスされても安全でなければなりません。

「Go言語による並行処理」から

WithValueでデータを保管するサンプル

値に型安全がない(interface{})サンプル
https://github.com/SatoTakeshiX/sample-go-context/blob/master/useValueWithInterface/contextValueInterface.go

値に型を定義したサンプル

https://github.com/SatoTakeshiX/sample-go-context/blob/master/useValueWithInt/contextValueInt.go

Contextで保存するべきデータ

公式ドキュメント

コンテキスト値はプロセスやAPIの境界を通過するリクエストスコープでのデータに絞って使いましょう。
関数にオプションのパラメーターを渡すために使うべきではありません。

「Go言語による並行処理」で言及されていた「リクエストスコープでのデータ」

(これを必ず守らなければ行けないものではないが、定義の指針となる作者の経験則)

  1. データはプロセスやAPIの境界を通過すべき
  2. データは不変であるべき
  3. データは単純な型に向かっていくべき
  4. データはデータであるべきでメソッド付きの型であるべきではない
  5. データは修飾の操作を助けるべきものであって、それを駆動するものではない

「Go言語による並行処理」によるAPIの各データと5つの経験則を満たすかどうか

データ 1 2 3 4 5
リクエストID
ユーザーID
URL
APIサーバーの接続
認可トークン
リクエストトークン

まとめ

公式ドキュメントと「Go言語による並行処理」を並べながらGoにおけるcontextパッケージの使い方を解説しました。
GoでAPIなどを作る上で役立つパッケージです。
ぜひ使いこなしていきましょう!

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away