読者です 読者をやめる 読者になる 読者になる

workerパターンをcontext化してみたら……

はい、というわけで、前記事のworkerパターンをcontextつかったらどーなるか、についてです。
前の記事や、その元記事のソースを読んでいる前提ですので、未読の方はそちらの確認からお願いします。

さて、ざっくりとした変更の方針ですけど、以下の2点です。

  • contextを使うことで、workerの実装側でキャンセルできるようにする
  • workerに渡す値はcontext経由にする。

というのをヤッツケでやってつくってみました

以下変更点の解説です。

まず、元実装ではworkerで実行される処理がベタ書きだったのを汎用化するため、dispatcherのqueueに入れるjobをとqueueの定義を変更します。

type (
  job struct {
    proc func(context.Context)
    ctx  context.Context
  }

  Dispatcher struct {
    queue chan *job
    // ...
  }
)

workerの実装も合わせて変更します。

go func() {
  defer d.wg.Done()
  for j := range d.queue {
    j.proc(j.ctx)
  }
}()

さらにdispatcherは親contextとCancelFunc持つようにします。

type (
  Dispatcher struct {
    // ...
    ctx    context.Context
    cancel context.CancelFunc
  }
)

そして使う側がcontextを拡張して使えるようにcontextを返す関数を用意します。

func (d *Dispatcher) Context() context.Context {
  return d.ctx
}

job登録もcontextを使わないパターンと使うパターンで2種類用意します。
使わないパターンでは、dispatcherのctxをそのまま渡します。

func (d *Dispatcher) Add(proc func(context.Context)) {
  d.queue <- &job{proc: proc, ctx: d.ctx}
}

func (d *Dispatcher) AddWithContext(proc func(context.Context), ctx context.Context) {
  d.queue <- &job{proc: proc, ctx: ctx}
}

終了処理も変更しています。

Immediatelyな実装の方では、queueの読み捨てをやめてcontextのcancelを先に呼ぶようにしました。
ちゃんとImmediatelyに帰ってくるかはjobの実装次第ですが、読み捨てよりはお行儀が良い気がしてます。

func (d *Dispatcher) StopImmediately() {
  d.cancel()
  close(d.queue)
  d.wg.Wait()
}

Immediatelyじゃない実装もworkerが全部終了した後ですが、cancel()を実行しています。コレをしないとリークが発生します

func (d *Dispatcher) Stop() {
  close(d.queue)
  d.wg.Wait()
  d.cancel()
}

さて、ここまでやったので、元のgetをcontextを受け取って使うようにちょちょいっと修正して……

func get(ctx context.Context) {
  url := ctx.Value("url").(string)
  req, err := http.NewRequest("GET", url, nil)
  if err != nil {
    log.Fatal(err)
  }

  resp, err := http.DefaultClient.Do(req.WithContext(ctx))
  // ...
}

jobを登録するトコをこんなカンジで、context.WithValueでurlを渡すように修正します。

  for i := 0; i < 10; i++ {
    url := fmt.Sprintf("http://placehold.it/%dx%d", i, i)
    ctx := context.WithValue(d.Context(), "url", url)
    d.AddWithContext(get, ctx)
  }

こんなカンジでworkerパターンのcontext対応化が完了しましたー。

感想

httpのclientとかはcontextに対応していて、中断処理に対応したモノをさっくり作ることができてラクですねー。


(8/24 追記)
id:lestrrat さんからマサカリが飛んできております((((;゚Д゚))))ガクガクブルブル

workerパターンをcontext化してみたら…… - okzkメモ

context.Contextを揮発性のないものでラップして持つのはよくないと思う。このほうがよりGoっぽいと思うが、どうだろう https://gist.github.com/lestrrat/c9b78369cf9b9c5d9b0c909ed1e2452e

2016/08/23 18:34
b.hatena.ne.jp

コチラについて、別エントリを書くかも……