LoginSignup
84
67

More than 5 years have passed since last update.

contextの使い方

Last updated at Posted at 2016-06-04

Go1.7で golang.org/x/net/context が
標準ライブラリに入るようなので改めて使い方のメモ。

変数の管理

ctx := context.Background()
ctx = context.WithValue(ctx, "Foo", 1)
ctx = context.WithValue(ctx, "Bar", 2)

fmt.Println(ctx.Value("Foo").(int)) // 1

よくあるやつ。値を代入するたびに新しいcontextが生成される。
なので、乱暴に値を上書かない限りはあまり状態を気にしなくていい。

TimeoutとCancel

func WithCancel func WithDeadline func WithTimeout はキャンセルを取り扱う。

WithTimeout, WithDeadline は特定時間になったタイミングでキャンセルされる。
引数で指定の仕方が 何秒後かと時刻指定がなのが違うだけ。

実際にWithTimeoutの中身は WithDeadlineを呼んでいるだけ。

WithTimeout, WithDeadline

指定した時間後にキャンセルイベントが起きる。

// 5秒かかる処理
func heavyFunc(ctx context.Context) {
  time.Sleep(5 * time.Second)
}

func main() {
  ctx := context.Background()
  ctx, cancel := context.WithTimeout(ctx, time.Second) // 1秒後にキャンセル
  defer cancel()

  go heavyFunc(ctx)
  go heavyFunc(ctx)

  select {
  case <-ctx.Done(): // 1秒以上かかると Done() がcloseされる
    fmt.Println("done:", ctx.Err()) // done: context deadline exceeded
  }
}

WithCancel

自分でキャンセルを使う場合に使う

func heavyFunc(ctx context.Context) {
  time.Sleep(5 * time.Second)
}

func main() {
  ctx := context.Background()
  ctx, cancel := context.WithCancel(ctx)

  go heavyFunc(ctx)
  go heavyFunc(ctx)

  cancel() // 即キャンセル

  select {
  case <-ctx.Done():
    fmt.Println("done:", ctx.Err()) // done: context canceled
  }
}

キャンセルの伝搬

親がキャンセルされると子にキャンセルが伝搬されていく

func main() {
  ctx := context.Background()

  parent, parentCancel := context.WithCancel(ctx)
  child, childCancel := context.WithCancel(parent)
  defer childCancel()

  go heavyFunc(ctx)

  go func() { // 親を1秒後にキャンセル
    time.Sleep(time.Second)
    parentCancel()
    fmt.Println("parent cancelled")
  }()

  select {
  case <-child.Done(): // 子もキャンセルされる
    fmt.Println("parent:", parent.Err()) // parent: context canceled
    fmt.Println("child:", child.Err()) // child: context canceled
  }
}

使い方

goroutineで処理を実行して、所定より時間がかかった場合にエラーを返すのに使う。
ただ、結果が欲しい場合は、contextとは違う形でchannelを使い、値を受け取って返すようにする。(だいたいこっちの使い方になる)

func heavyFunc(ctx context.Context) error {
  time.Sleep(time.Second)
  return nil
}

func exec(ctx context.Context) error {
  errChan := make(chan error, 1)

  go func() {
    errChan <- heavyFunc(ctx)
  }()

  select {
  case <-ctx.Done(): // キャンセルが発生した場合
    return ctx.Err()
  case err := <-errChan: // heavyFuncの結果を取得した場合
    return err
  }
}

func main() {
  ctx := context.Background()
  ctx, cancel := context.WithTimeout(ctx, 2*time.Second) // 全体2秒でタイムアウト
  defer cancel()

  if err := exec(ctx); err != nil {
    log.Fatal(err)
  } else {
    log.Print("ok")
  }
}

まとめ

contextを使うと、値の取り回しやキャンセル処理は楽になる。だけど、goroutineを大量に使って細かく状態を管理をやってくれるものではないので注意。

参考

x/net/context の実装パターン

84
67
0

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
84
67