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

# 概要

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

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

# 開発環境

```bash
$ go version
go version go1.10 darwin/amd64
```

# はじめのサンプルコード

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

[「Go言語による並行処理」](https://www.oreilly.co.jp/books/9784873118468/)にあったものをサンプルコードを私のGitHubに載せています。


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

## チャネルのみを使う



サンプルコードはこちら

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31587/b6fb511f-a9b2-ac14-8dd3-e914204031f4.png)



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


## contextを使う

サンプルコードはこちら

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31587/35ae007b-ba93-124f-3022-99acfe368049.png)


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




# contextパッケージの定義

[公式ドキュメント](https://godoc.org/context)からcontextの定義をみてみましょう。

## Variables

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

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

```go
var DeadlineExceeded error = deadlineExceededError{}
```

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

## func WithCancel


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


## func WithDeadline


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

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

## func WithTimeout


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

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


**サンプルコード**

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

**code**

```go
// 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**

```bash
context deadline exceeded
```

## type CancelFunc


```go
type CancelFunc = context.CancelFunc
```

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

## type Context




```go
type Context = context.Context
```

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

## func Background 


```go
func Background() Context
```

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


## func TODO

```go
func TODO() Context
```

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


## func WithValue

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

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

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

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


# [「Go言語による並行処理」](https://www.oreilly.co.jp/books/9784873118468/)から

## 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などを作る上で役立つパッケージです。
ぜひ使いこなしていきましょう！

# 参考文献

* [context - GoDoc](https://godoc.org/golang.org/x/net/context#pkg-examples)
* [O'Reilly Japan - Go言語による並行処理](https://www.oreilly.co.jp/books/9784873118468/)