16.3 ゴルーチンプールを使って複数のタスクを実行

ゴルーチン自体の消費リソースは少なく、起動時間も短いことは紹介しました。OSスレッドを使う場合には、1スレッドあたり2MBのメモリを使うことがあります。タスク数が1,000にもなると、確保するメモリも相当な量になりますし、スレッドの作成をOSへ依頼する時間的なコストもかかるため、タスクの数だけスレッドを起動するのは得策ではありません。ゴルーチンのコストはその数百分の1なので、簡単な並行処理であればタスクの数だけゴルーチンを起動してしまうことがリーズナブルな選択になることも少なくありません。

しかしゴルーチンに実行させたい仕事が、巨大な画像ファイルを扱うような重い処理のときには、全タスクを同時に実行できません。同時実行数を制限しながらタスクをこなす方が良いでしょう。また、CPUヘビーな仕事の場合、CPUコア数以上に並列数を増やしてもパフォーマンスは上がりません。

このような場合、あらかじめゴルーチンをいくつか起動しておき、それぞれのゴルーチンがタスク入力用のチャネルから入力を受け取って処理し、結果を出力するチャネルへと送信する構造になるでしょう。

func Worker(tasks <-chan Task, results chan<- Result) {
	for t := range tasks {

		// ここで何かタスクを行う

		// 結果の送信
		results <- Result{
			Value: v,
			Err:   err,
		}
	}
}

このチャネルにタスクをどのように入れるかで、大きく方針が変わります。

16.3.1 タスクの分量がわからない場合

事前にタスクの量がわからず、タスクの総量を探索的に把握しながら、裏でワーカーを走らせ始める、 ...