[Golang]ループ処理内でgoroutineを使う時に考慮しておくべきこと

こんにちは。KOUKIです。

とあるWeb系企業で、Go言語を使った開発業務に従事しています。

本記事では、ループ処理内でgoroutineを使う際に考慮しておくべきことをまとめました^^

ちなみに、goroutineは、プログラムの処理を並行化させるGoの素晴らしい機能です。

goroutineを使わないバージョン

次のコードを見てください。

このコードは、10万回のループ後に、実行結果と経過時間を標準出力に表示する簡単なプログラムです。

2.1秒で完了しました。けっこう早いですね^^

goroutineを使うバージョン

goroutineを使うと先ほどの処理をもっと早く実行できます。しかもかなり簡単に!

しかし、考慮すべき点があるので、一緒に考えていきましょう。

無名関数をgoroutine化

gorutine化には「go」キーワード使います。これを並行化させたい関数の前に付与するだけで、処理を並行化させることが可能です。

プログラムを実行してみましょう。

めっちゃ早くなっ….いや違いますね。

「Done: 97635 times」となっているので、何かがおかしいです。

考慮1: WaitGroupで完了待ちにする

実は、goroutineは別プロセスで動くスレッドです。メインプログラム(main)の動作が完了した時点で、スレッドが稼働中か否かに関係なくプログラムを停止してしまいます。

そのため、次の考慮が必要になります。

goroutine化に伴う考慮 * スレッドの完了を待つ

スレッドの完了を可能にするのが、syncパッケージのWaitGroupです。これで、別スレッドの実行が完了するのを待つことができます。

プログラムを実行してみましょう!

だめですね。。

考慮2: 排他制御をする

まだ、考慮すべきことがあります。

プログラム内で使用されているcount変数は、全てのgoroutineの中で同じメモリをみています。つまり、同じメモリに対して、書き込み、読み込み処理をしているわけです。

そのため、次の考慮が必要になります。

goroutine化に伴う考慮 * データの書き込み、読み込み時には排他制御をかける

排他制御をかけるには、syncパッケージのMutexを使います。

今度こそOKですね。

考慮3: WaitGroupとMutexは一緒に使うべきか

ちなみに、WaitGroupを削除したらどうなるのでしょうか。

プログラムを実行します。

やはり、うまく動きませんね。

そのため、次の考慮が必要になります。

goroutine化に伴う考慮 * WaitGroupとMutexは一緒に使う

考慮4: gorutineの最大数の制御

goroutineを作りすぎると逆に遅くなるらしいので、channelでgoroutineの最大数を制御してみましょう。

channelは、容量以上のデータを追加したり、逆に空のデータを読み込もうとした場合、処理がブロックされます。

このサンプルコードの例だと、容量を10と指定しているので、11以上のgoroutineを作られないようにしました。

プログラムを実行してみましょう。

逆に遅くなってしまいましたね^^;
Channelでブロックしてるからだと思います。

しかし、goroutineの上限数の制御は、考慮に入れておいた方がいいです。

goroutine化に伴う考慮 * goroutineの最大数に上限をもうけたほうが良い

goroutineの最大数の制御は絶対にやったほうがいいことではありませんが、メモリの枯渇などマシンのリソースにシビアなシステムの場合は、goroutineに気を配ったほうが良いと思います。

考慮5: goroutineのエラーの受け取り方

goroutineは、別スレッドで動くプログラムです。エラーを受け取ることは可能なのでしょうか?

このコードは、コンパイルエラーになります。

エラーを受け取れないのか、、、と思っていたところ、以下のように実装してみました。

普通に無名関数で包めばOKですね。

goroutine化に伴う考慮 * goroutineでエラーを受け取りたい場合は、無名関数で包む

ちなみに、Channelで受け取る方法もあります。

考慮6: goroutineの複数のエラーの受け取り方

goroutineで発生した複数のエラーを受け取りたい場合は、multierrが便利です。

multierr.Append」で、発生した複数のエラーを束ねることができます。

実行結果を見てみましょう。

こんな感じで、エラーを束ねられます。

goroutine化に伴う考慮 * goroutineで複数のエラーを受け取るのに、multierrが便利

まとめ

以下、まとめです。

goroutine化に伴う考慮 * スレッドの完了を待つ
* データの書き込み、読み込み時には排他制御をかける
* WaitGroupとMutexは一緒に使う
* goroutineの最大数に上限をもうけたほうが良い
* goroutineでエラーを受け取りたい場合は、無名関数で包む
* goroutineで複数のエラーを受け取るのに、multierrが便利

もっと考慮するべきことがあるだろ!という感じかもしれませんが、ひとまずこの辺にします^^

また何か気がついたら追加していきますので、よろしくお願いします!

それでは、また!

Go記事まとめ

コメントを残す