適宜書き足し、修正するかも
時間があったのでgo言語の入門。結局のところ、並行処理が(go言語を採用する場合などの)キモ。 そうでなければJavaScriptとかPythonで良いのではないかと。
ただ、goroutineやchannelは他の言語にはみられないgo言語独特の仕様である。 しかもこれらは自由度が高いので、何も考えないとぐちゃぐちゃになりがち。
書き方のパターンを決めておいた方が良さそう。
--
- channel
- ストリーム用途として
- シグナル用途として
- 複数の異なる種類の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
関数)の中にある、と明示できる。
- resは戻り値になる。戻り値でresを受信チャンネルと制限することで送信部分がこの関数(
- 単一のgoroutineを扱う場合
- このgoroutine内部で
defer close(ch)
とする
- このgoroutine内部で
- 複数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
- 複数のチャンネルになる場合は、faninでまとめる
- ストリームの始端の場合
- この場合は引数に受信チャンネルを入れようがない
- シグナル(signal, timeout, contextなど)の作成の場合もある意味ストリームの始端と言える
- 一つの値を渡す場合