はじめに
マルチスレッドプログラミングで処理を並列化する際、「一体いくつのスレッドを立ち上げるのが最も効率的なのか?」という問題に直面します。スレッド数をやみくもに増やしても、CPUコア数以上のスレッドは、結局のところCPU時間を奪い合うだけになり、かえってパフォーマンスが低下することがあります。
最適なス-レッド数を決定する一つの目安となるのが、「ハードウェアが同時に実行できるスレッドの数」です。C++11の標準ライブラリ <thread>
には、この数値を簡単に取得するための std::thread::hardware_concurrency()
という便利な静的メンバ関数が用意されています。
この記事では、hardware_concurrency()
を使って、実行環境に最適なスレッド数を取得し、その数だけスレッドを起動する方法を解説します。
hardware_concurrency()
を使ったサンプルコード
このコードは、まず hardware_concurrency()
で並行実行可能なスレッド数を取得します。そして、その数だけワーカースレッドを生成し、それぞれのタスクを実行させます。
完成コード
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
// 各スレッドで実行されるタスク
void worker_task(int id) {
cout << "タスク " << id << " を開始します。" << endl;
// 何か重い計算処理をシミュレート
this_thread::sleep_for(chrono::seconds(1));
cout << "タスク " << id << " を完了しました。" << endl;
}
int main() {
// 1. ハードウェアがサポートする並行スレッド数を取得
unsigned int num_threads = thread::hardware_concurrency();
// 2. 取得できなかった場合のフォールバック
if (num_threads == 0) {
cout << "並行スレッド数を取得できませんでした。デフォルトの2スレッドで実行します。" << endl;
num_threads = 2; // 取得失敗時のデフォルト値を設定
} else {
cout << "この環境では " << num_threads << " 個のスレッドが並行実行可能です。" << endl;
}
// 3. 取得した数だけスレッドを生成
vector<thread> threads;
for (unsigned int i = 0; i < num_threads; ++i) {
threads.emplace_back(worker_task, i + 1);
}
// 4. 全てのスレッドが終了するのを待つ
for (auto& th : threads) {
th.join();
}
cout << "全てのタスクが完了しました。" << endl;
return 0;
}
コードの解説
unsigned int num_threads = thread::hardware_concurrency();
これが、並行スレッド数を取得する核心部分です。
thread::
:hardware_concurrency()
は、特定のthread
オブジェクトに属するメンバ関数ではなく、thread
クラスそのものに関連付けられた静的メンバ関数です。そのため、クラス名::関数名
という構文で呼び出します。- 戻り値:
- 多くの環境では、CPUの論理プロセッサ数(コア数 × ハイパースレッディング数)を
unsigned int
型で返します。例えば、4コア8スレッドのCPUなら8
が返されます。 - しかし、環境によってはこの情報を取得できず、
0
を返す場合があります。
- 多くの環境では、CPUの論理プロセッサ数(コア数 × ハイパースレッディング数)を
if (num_threads == 0)
hardware_concurrency()
が 0
を返した場合に備えた、フォールバック処理です。0
が返された場合、スレッドを一つも生成しないとプログラムが意図通りに動作しない可能性があるため、2
や 1
のような、最低限動作するデフォルト値を設定しておくのが安全な実装です。
まとめ
今回は、std::thread::hardware_concurrency()
を使って、実行環境のハードウェアがサポートする並行スレッド数を取得する方法を解説しました。
std::thread::hardware_concurrency()
を呼び出すだけで、最適なスレッド数の目安が得られる。- この関数は静的メンバ関数である。
- 戻り値が
0
になる可能性を考慮し、フォールバック処理を記述しておくことが推奨される。
CPUリソースを最大限に活用するような計算集約型のタスクを並列化する際に、この関数で得られた値を基にスレッド数を決定することで、様々な実行環境で良好なパフォーマンスを発揮する、移植性の高いプログラムを作成することができます。