
コンピュータプログラミングにおいて、スレッドプールは、コンピュータプログラムの実行の並行性を実現するためのソフトウェア設計パターンです。複製ワーカーまたはワーカークルーモデルとも呼ばれるスレッドプール[ 1 ]は、監視プログラムによって並行実行用に割り当てられるタスクを待機する複数のスレッドを維持します。スレッドプールを維持することで、このモデルはパフォーマンスを向上させ、短命なタスクのためにスレッドが頻繁に作成および破棄されることによる実行の遅延を回避します。[ 2 ]もう1つの優れた特性は、利用可能なスレッド数よりも少ないスレッドを使用することで、システム負荷を制限できることです。利用可能なスレッド数は、実行完了後の並列タスクキューなど、プログラムが利用できるコンピューティングリソースに合わせて調整されます。
スレッドプールのサイズは、タスクを実行するために確保されるスレッドの数です。これは通常、アプリケーションで調整可能なパラメータであり、プログラムのパフォーマンスを最適化するために調整されます。[ 3 ]最適なスレッドプールのサイズを決定することは、パフォーマンスを最適化するために重要です。
スレッドプールを使用する利点の 1 つは、タスクごとに新しいスレッドを作成する場合と比べて、スレッドの作成と破棄のオーバーヘッドがプールの初期作成時のみに限定されるため、パフォーマンスとシステムの安定性が向上する可能性があることです。スレッドとその関連リソースの作成と破棄は、時間の面でコストのかかるプロセスになる可能性があります。しかし、予備のスレッドが多すぎるとメモリが無駄になり、実行可能なスレッド間のコンテキスト切り替えによってパフォーマンスが低下します。別のネットワーク ホストへのソケット接続は、切断と再確立に多くの CPU サイクルを要する可能性がありますが、複数のネットワーク トランザクションにわたって存続するスレッドに関連付けることで、より効率的に維持できます。
スレッドの起動時間を考慮に入れなくても、スレッドプールの使用は有用な場合があります。スレッドプールの実装では、手動でスレッドを管理する場合よりも簡単にできる高レベルで、作業のキューイング、並行性の制御、スレッドの同期が容易に行えます。[ 4 ] [ 5 ] これらの場合、使用によるパフォーマンス上の利点は二次的なものとなる可能性があります。
通常、スレッドプールは単一のコンピュータ上で実行されます。しかし、スレッドプールは概念的にはサーバーファームと関連しており、マスタープロセス(それ自体がスレッドプールである場合もある)がタスクを異なるコンピュータ上のワーカープロセスに分散させることで、全体の処理能力を向上させます。並列処理が容易な問題は、このアプローチに非常に適しています。
スレッド数は、待機中のタスク数に基づいて、アプリケーションの実行中に動的に調整される場合があります。たとえば、Webサーバーは、多数のWebページリクエストが届いた場合にスレッドを追加し、リクエストが減少した場合はスレッドを削除できます。スレッドプールを大きくすると、リソース使用量が増加します。スレッドの作成または破棄のタイミングを決定するために使用されるアルゴリズムは、全体的なパフォーマンスに影響を与えます。
bashでは、 xargsの--max-procs/によって実装される場合、例えば次のようになります。-P
# 5 つの URL を並列で取得しますurls =( "https://example.com/file1.txt" "https://example.com/file2.txt" "https://example.com/file3.txt" "https://example.com/file4.txt" "https://example.com/file5.txt" )printf '%s\n' " ${ urls [@] } " | xargs -P 5 -I {} curl -sI {} | grep -i "content-length:"Go言語では、ワーカープールと呼ばれる。
パッケージメインimport ( "fmt" "time" )func worker ( id int , jobs <- chan int , results chan <- int ) { for j := range jobs { fmt . Println ( "worker" , id , "started job" , j ) time . Sleep ( time . Second ) fmt . Println ( "worker" , id , "finished job" , j ) results <- j * 2 } }func main () { const numJobs = 5 jobs := make ( chan int , numJobs ) results := make ( chan int , numJobs )for w := 1 ; w <= 3 ; w ++ { go worker ( w , jobs , results ) }for j := 1 ; j <= numJobs ; j ++ { jobs <- j } close ( jobs )for a := 1 ; a <= numJobs ; a ++ { <- results } }印刷されます:
$ time go run worker-pools.goワーカー 1 がジョブ 1 を開始しましたワーカー 2 がジョブ2 を開始しました ワーカー3 がジョブ 3 を開始しましたワーカー 1 がジョブ 1 を完了しました ワーカー 1 がジョブ 4 を開始しましたワーカー 2 がジョブ 2 を完了しましたワーカー 2 がジョブ 5 を開始しましたワーカー 3 がジョブ 3 を完了しましたワーカー 1 がジョブ 4 を完了しました ワーカー 2 がジョブ 5 を完了しました実測値 0分2.358秒リソースを効率的に管理するもう1つの方法は、リクエストチャネルからすべて読み取る固定数のハンドルゴルーチンを開始することです。ゴルーチンの数は、処理への同時呼び出しの数を制限します。
ワーカー プールは、固定数の m 個のワーカー (Go ではゴルーチンで実装) が、ワーク キュー (Go ではチャネルで実装) 内の n 個のタスクを順番に処理していくモデルです。ワーカーが現在のタスクを完了し、新しいタスクを取り出すまで、作業はキュー内に留まります。