goroutineとchannelについて

適宜書き足し、修正するかも


時間があったのでgo言語の入門。結局のところ、並行処理が(go言語を採用する場合などの)キモ。 そうでなければJavaScriptとかPythonで良いのではないかと。

ただ、goroutineやchannelは他の言語にはみられないgo言語独特の仕様である。 しかもこれらは自由度が高いので、何も考えないとぐちゃぐちゃになりがち。

書き方のパターンを決めておいた方が良さそう。

--

  • channel
    • ストリーム用途として
    • シグナル用途として
      • done(生のチャンネル)
        • context.WithCancel(context)
        • exec.CommandContext
      • signal(os.Signal), signal.NotifyContext(Go 1.15以降)
      • timeout(time.After)
        • context.WithDeadline(time.Time), context.WithTimeout(duration)
        • time.Ticker
      • errGroup
      • C言語のファイルディスクリプタ(open, epoll_create, signalfd, eventfdなどの戻り値)の進化版と捉えるとよいかも
    • 複数の異なる種類のchannelをまとめる際はselectを使うと良い
    • 複数の同じ種類のチャンネルをまとめる際
      • fanin, orDoneパターンを利用
      • selectでもよい
  • func do_job(inCh <-chan interface{}) <-chan interface{};のような形を基本形とする
    • 例) コード例 こういう感じ
    • こうすると、引数、戻り値でチャンネルを受信に制限することができる。
      • 全体のコードの見通しがよくなり可読性が増す
        • 特に戻り値の送信部分はdo_jobの中でやっているということがすぐに分かりうれしい
    • functionの中にgoroutineを書くようにする
      • go do_sth()の構文は使わない。
      • goroutineの中にdo_calc相当のロジックを入れるとよい
        • for val := range ch { do_calc(val) }など
          • 複数のgoroutineの中で上のように同じchannelを消費しても問題ない。(というか良い書き方)
        • この中でres <- valのように送信すると良い。
          • resは戻り値になる。戻り値でresを受信チャンネルと制限することで送信部分がこの関数(do_job関数)の中にある、と明示できる。
    • 単一のgoroutineを扱う場合
      • このgoroutine内部でdefer close(ch)とする
    • 複数goroutineを扱う場合
      • goroutineの内部はwaitgroupやerrgroupで管理する
      • 各goroutineはループ変数を扱う場合は引数としてバインドしておく
      • 別goroutineでdefer close(ch)する。wg.Waitで複数goroutineの全終了をトリガーにすれば良い。
        • 例): コード例
        • [write] もし、複数goroutineの全終了を待たずにcloseして、その後あるgoroutineがchannelに書き込んでしまうと、panicになる!
        • [read] for j := range inChのように管理するとclose後はループから抜けてくれるので、複数gouroutineがあろうが問題ない。
          • v := <-inChとするとclose直後にv=0となってしまうのでその後のバグの予兆となってしまう。特にselect文で注意する
    • ただの値、リストではなく(受信)チャンネルを戻り値とする
      • [理由1] 後続(のストリーム)に渡せる
      • [理由2] <-do_job()で同期(sync)できる
      • [理由3] 戻り値を無視すれば非同期(async, promise)になる
      • ストリームの終端の場合でも(値やvoidではなく)channelを返すようにすると、拡張性が増す。
  • [例外1] func do_job(in interface{}) <-chan interface{}となる場合
    • 一つの値を渡す場合
      • 複数のチャンネルになる場合は、faninでまとめる
        • [例] multiplex
    • ストリームの始端の場合
      • この場合は引数に受信チャンネルを入れようがない
      • シグナル(signal, timeout, contextなど)の作成の場合もある意味ストリームの始端と言える

参考url