雑なメモ書き

気楽にいきます

Contextとは何者なのか考えてみた(1)

  • goでですね、context.Contextがでてから割と立つんですが
  • 並行まわりを全部これでやらないといけ無いんじゃ無いかという強迫観念にとりつかれませんか
  • 素のchannnelやsync.WaitGroupとか使った方が早そうなんだけど
  • 違う道があるんじゃ無いかと考えてしまう
  • そこで、もう一度contextに対して色々いじって考えてみることにした

context.Contextとは何か?

  • 簡単に言うと以下のメソッドを要求するinterface
    • DeadLine
    • Done
    • Err
    • Value
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • 要求するメソッドを実装してさえ居ればContextと見なされます
  • これだけではなんなんで、言い換えてみると
  • timeoutとcancelを表現する為の共通の枠組みです

実際の実装: WithCancel

  • よく使われるContextに代入される実装は
  • contextに生えているメソッドから利用できます
  • その中でcancelを実装しているWithCancelが分かりやすいかと思うので
  • これを追います
  • 構造体はdoneチャンネルを保持しています
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     chan struct{}         // created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}
  • 細かい部分をはしょりますが
  • WithCancelが返すcancelの中身で以下の様にdonechannnelをcloseしています
if c.done == nil {
    c.done = closedchan
} else {
    close(c.done)
}
  • 更にこのdoneはDoneで参照されていて
  • closeされた場合はcloseされたチャンネルが返ります
  • つまり、渡したctxを介してchannelを使用してキャンセル処理を行うことができます
func (c *cancelCtx) Done() <-chan struct{} {
    c.mu.Lock()
    if c.done == nil {
        c.done = make(chan struct{})
    }
    d := c.done
    c.mu.Unlock()
    return d
}
  • ここでよくあるcontextの定型処理をみます
  • channnelがcloseされていればcase文の中身が実行されて終了します
 select {
  case <-ctx.Done():
      fmt.Println("end")
      return
}

ここでふたたびcontextとは何か?

  • 現状channelを介してgoroutineへキャンセル処理を伝播させることの出来る共通の書式だと考えています。
  • この仕組みの導入によりDoneに相当する処理が彼方此方で書かれなくてもよくなり
  • contextを見たらそう言う処理があるんだろうなということが認識できるから分かりやすくなる
  • 本題はここからで、この枠組みをもって並列化させる仕組みは綺麗に書くことが出来るのか
  • sync.WaitGroupはどんな感じになっているかなどを次回以降に書く気になったら書きます

参考