Goワーカー・パイプライン設計
Go応用と非同期パターンの基礎
Go言語は軽量なゴルーチンとチャネルを備えており、非同期パターンを簡潔に実装できます。典型的な例として、複数のタスクを同時に実行し、結果をチャネルで受け取る「ゴルーチン+チャネル」パターンがあります。これにより、I/O待ちや計算負荷を分散し、スレッド数を増やさずに高いスループットを実現できます。
func worker(id int, jobs <-chan int, results chan<- int) {
for n := range jobs {
results <- n * n
}
}
上記のように、ジョブをチャネルに投入し、複数のworkerが並行して処理することで、非同期処理を安全に行えます。
Worker PoolとPipelineで実現する効率化
Worker Poolは固定数のゴルーチンを再利用し、タスクを順次処理することでリソースを最適化します。Pipelineは複数段階の処理をチェーン化し、各段階が独立したWorker Poolとして動作します。これにより、データフローを可視化しやすく、スケーラビリティと効率化が同時に達成できます。
func pipeline() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 0; w < 5; w++ {
go worker(w, jobs, results)
}
for i := 0; i < 100; i++ {
jobs <- i
}
close(jobs)
for i := 0; i < 100; i++ {
fmt.Println(<-results)
}
}
この構造は、CPUバウンドとI/Oバウンドの両方に対して有効で、スケーラビリティを高めつつ、コードの可読性も保ちます。
Fan-out/Fan-inとContext伝播でスケーラビリティを高める
Fan-out/Fan-inパターンは、1つの入力を複数のゴルーチンに分散し、結果を集約する手法です。Contextを利用すると、キャンセルやタイムアウトを全ゴルーチンに伝播させ、リソースリークを防止できます。
func fanOut(ctx context.Context, urls []string) []string {
var wg sync.WaitGroup
results := make(chan string, len(urls))
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
req, _ := http.NewRequestWithContext(ctx, "GET", u, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil { return }
body, _ := io.ReadAll(resp.Body)
results <- string(body)
}(url)
}
go func() {
wg.Wait()
close(results)
}()
var out []string
for r := range results {
out = append(out, r)
}
return out
}
Contextを使うことで、全ゴルーチンが同時にキャンセルされ、スケーラビリティと安全性が向上します。
デザインパターンとしての並行パターンまとめ
Goでの並行設計は、デザインパターンと密接に結びついています。Worker Pool、Pipeline、Fan-out/Fan-inはそれぞれ「プロデューサ―/コンシューマー」「ストリーム」「分散処理」のパターンに対応し、Context伝播は「キャンセル/タイムアウト」のデコレータとして機能します。これらを組み合わせることで、スケーラビリティと効率化を両立させる堅牢なアーキテクチャが構築できます。
実際のプロダクションコードでは、これらのパターンを適切に選択し、テストとモニタリングを組み合わせることで、安定した高性能サービスを提供できます。
コメント
コメントを投稿