[golang]goroutineの使い所1~時間のかかるデータ検索~

こんにちは。KOUKIです。

Go言語を使って、Webシステム開発をしてます。

Go言語マスターを目指して日々精進していますが、そのために確実にマスターしたい技術がgoroutineです。皆さんは、goroutineを使いこなせているでしょうか?

goroutineは、並行処理を簡単に実装できるGo言語の特筆すべき機能の一つです。

本記事では、goroutineの使い所の一つをサンプルコードとともに紹介します。

めちゃくちゃ時間のかかる検索処理に使おう!

プログラムの処理内で、めちゃくちゃ時間のかかる、かつ並行で走らせても結果に影響が出ない処理の場合、goroutineで並行処理した方が早い場合があります。

例えば、データ検索が該当します。

特定のキーワードを元に、データベースにアクセスし、その結果を取得するような処理です。

逐次処理の場合

最初に逐次処理の場合を確認してみましょう。

ここでは実際のDBではなく、メモリに保存した変数に対して、検索をかけるプログラムを作成しました。

find関数内で引数に指定されたIDを元に情報を取得しますが、かなり時間がかかる処理であることトレースするために、time.Sleepにて1秒間スリープさせています。

getBookTitleNormalでは、1~30までのIDの情報を検索し、その結果を呼び出し元に返す処理を実装してます。

このプログラムを実行すると、完了までに30秒かかります。

並行処理の場合

さて、この遅すぎるプログラムを改善するにはどうしたらいいでしょうか?

そうです! goroutineを使って並行で処理すればいいのです。

getBookTitleParallelは簡単な並行処理の実装ですが、テクニックが一杯詰まっています。

まずは、このプログラムを実行してみましょう。

今度は3秒で完了しました。約1/10の短縮です。

ここで使ったテクニックを3つほど紹介します。

テクニック1 – goroutineの最大数を制御する

goroutineを扱う際に留意すべきことの一つが、goroutineを作りすぎてしまうと逆にパフォーマンスが悪くなるという事実です。

このプログラムの規模ではgoroutineを作りすぎてもあまり影響はありませんが、goroutineの増加に伴って、パフォーマンスが悪くなるのを現場でみました。goroutineを作るのにもある程度コストがかかるんですかね?

そのため、goroutineの最大数を制御するsemapthore Channelを作成しています(名前は任意です)。

上記の設定によりチャネルには最大10個の値しか格納することができません。for文の中で書き込み(semaphore <- struct{}{})を行なっていますが、容量に空きがない場合は、プログラムのどこかで読み込み処理が行われるまで、ここでブロックします。これにより処理が一時停止するので、goroutineが最大10個までしか作られなくなります。

テクニック2 – 無名関数でクリーンアップ

WaitGroupやChannelの読み込みを確実に実行したい場合は、無名関数にdeferキーワードをつけて実装するとプログラムが異常終了しても確実に実行されるので便利です。

特にsemaphore Channelはここで確実にブロック解除(読み込み)しておきたいので、このように記述しておくといいと思います。※解除できなかった場合は、影響にブロックされ続けます

テクニック3 – 排他制御する

排他制御をしていない場合、メモリへの同時書き込みが発生する可能性があるのが、並行処理プログラミングの怖いところです。

しかし、Go言語には、Mutexという排他制御するための便利な機能がデフォルトで備わっているので、これを活用しましょう。

Lockにて排他制御をかけ、UnLockで解除できます。deferを使っているので、関数の処理が完了した時に排他制御を解除してくれます。

おわりに

いかがだったでしょうか。

並行処理プログラミングは難しいですが、Go言語だとgoroutineがあるため、参入障壁がめちゃくちゃ低いです。

この機会にぜひ学習されることをオススメします!

Go記事まとめ

オススメ書籍