Go言語のgoroutineの数を制御する方法とベストプラクティス!初心者向け解説
生徒
「先生、goroutineって便利だけど、たくさん作りすぎるとどうなるんですか?」
先生
「goroutineは軽量ですが、無制限に作るとメモリを消費したり、スケジューリングの負荷が増えます。だから数を制御することが大切です。」
生徒
「数を制御する方法はあるんですか?」
先生
「はい、channelやセマフォを使って同時に実行するgoroutineの数を制限する方法があります。それでは順番に見ていきましょう。」
1. goroutineの数を制御する必要性
goroutineは非常に軽量で、数千、数万単位で作成できます。しかし、無制限に作るとCPUやメモリの負荷が高まり、プログラム全体のパフォーマンスが落ちることがあります。
そのため、大量のタスクを並列で処理する場合は、同時に実行するgoroutineの数を制御することがベストプラクティスです。
2. channelでgoroutineの数を制御する例
channelを使うと、同時に実行するgoroutineの数を簡単に制御できます。ここでは、セマフォのように動作するパターンを紹介します。
package main
import (
"fmt"
"time"
)
func worker(id int, sem chan struct{}) {
fmt.Printf("goroutine %d 開始\n", id)
time.Sleep(1 * time.Second)
fmt.Printf("goroutine %d 終了\n", id)
<-sem // セマフォを解放
}
func main() {
maxGoroutines := 3
sem := make(chan struct{}, maxGoroutines) // セマフォとして機能
for i := 1; i <= 10; i++ {
sem <- struct{}{} // セマフォを取得
go worker(i, sem)
}
// セマフォが空になるまで待機
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}
}
この例では、同時に最大3つのgoroutineしか実行されません。semチャンネルを使って、goroutineの数を制御しています。
3. waitgroupで全goroutineの完了を待つ
goroutineの完了を待つには、sync.WaitGroupを使うのが基本です。これにより、全てのgoroutineが終了するまで処理を待機できます。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("goroutine %d 開始\n", id)
time.Sleep(500 * time.Millisecond)
fmt.Printf("goroutine %d 終了\n", id)
}
func main() {
var wg sync.WaitGroup
maxGoroutines := 2
sem := make(chan struct{}, maxGoroutines)
for i := 1; i <= 5; i++ {
wg.Add(1)
sem <- struct{}{}
go func(id int) {
defer func() { <-sem }()
worker(id, &wg)
}(i)
}
wg.Wait()
fmt.Println("全てのgoroutineが終了しました")
}
この例では、maxGoroutinesで同時実行数を制御し、WaitGroupで全goroutineの完了を待っています。
4. ベストプラクティス
- 大量のgoroutineを無制限に作らない
- channelやセマフォで同時実行数を制御する
- WaitGroupを使って全goroutineの完了を確実に待つ
- goroutine内でのpanicに注意し、必要に応じてrecoverを使う
- 長時間実行する処理にはcontextパッケージを組み合わせてキャンセル処理を導入する
これらの方法を組み合わせると、安全かつ効率的に並行処理を行えます。
5. goroutine制御のまとめ的ポイント
goroutineを制御することは、パフォーマンス向上と安全な並行処理のために重要です。
channelやWaitGroupを活用し、同時実行数を制限することはベストプラクティスとして覚えておきましょう。