ワーカーとスレッドプール

Go言語における「ワーカー」と「スレッドプール」は、高度な並行処理の設計パターンの一種であり、大量のタスクを効率的に処理する際に利用されます。GoではGoroutineを使って軽量な並行処理が可能なため、この仕組みを使ってワーカーとスレッドプールを構築することが一般的です。


1. 基本概念

ワーカーパターン(Worker Pattern)

ワーカーパターンとは、一定数の「ワーカー(Goroutine)」を立ち上げ、それぞれが共通のタスクキュー(channel)から仕事を引き受けて処理する方式です。

  • メリット:

    • タスクの処理速度が安定する

    • 過剰なGoroutineの生成を防ぐことで、メモリ消費を抑制

  • 構成要素:

    • タスクキュー(channel)

    • ワーカー群(Goroutine)

    • タスクの生産者(Producer)

スレッドプール(Thread Pool)との違い

Goにおける「スレッドプール」は厳密には存在せず、Goroutineを用いた**ワーカープール(Worker Pool)**が代替手段として用いられます。GoランタイムがGoroutineとOSスレッドのマッピングを内部的に管理しており、ユーザーが直接スレッドを管理することはありません。


2. 実装例

以下は基本的なワーカープールのサンプルです。

go
package main import ( "fmt" "math/rand" "sync" "time" ) // ワーカーが処理する仕事の型 type Job struct { ID int Payload int } // ワーカーの定義 func worker(id int, jobs <-chan Job, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { fmt.Printf("Worker %d processing job %d with payload %d\n", id, job.ID, job.Payload) time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) // 模擬的な処理 } } func main() { const numWorkers = 4 const numJobs = 10 jobs := make(chan Job, numJobs) var wg sync.WaitGroup // ワーカーを起動 for i := 1; i <= numWorkers; i++ { wg.Add(1) go worker(i, jobs, &wg) } // タスク投入 for j := 1; j <= numJobs; j++ { jobs <- Job{ID: j, Payload: rand.Intn(1000)} } close(jobs) // ジョブチャネルを閉じて終了を通知 wg.Wait() // 全ワーカーの完了を待機 }

3. 応用と最適化

  • バッファ付きチャネルの利用: タスク投入側が詰まらないように、channelにバッファを持たせることでスループットが向上する。

  • コンテキスト(context.Context)によるキャンセル処理: 外部からの中断に対応するために、ワーカー内部でコンテキストを監視させる。

  • エラーハンドリングの集中管理: 結果を別チャネルに送って、集約的にログや処理結果を記録できる。


4. 利用例

  • HTTPサーバーでのリクエスト処理

  • バッチ処理の並列化

  • 大量のファイル/データの並行処理

  • メッセージキュー(RabbitMQ, Kafka)との連携処理


まとめ

Goにおける「ワーカーとスレッドプール」は、軽量なGoroutineとチャネルの組み合わせにより、高効率な並行処理を実現するための強力な手段です。OSスレッドの管理をGoランタイムに任せつつ、開発者はワーカー数やタスク設計に集中できる点が大きな特徴です。

生成日:2025/05/03