C++: マルチスレッド基本
最終更新:2026-08-31
レッスン42では、正規表現について学びました。
ここでは、C++の並列処理領域——マルチスレッドプログラミングについて学びます。
今日のプログラムは、複数のスレッドで並列実行し、パフォーマンスを向上させます。
マルチスレッドは上級プログラマ必須のスキルで、パフォーマンスを向上させます。
1. マルチスレッドとは
(1) 1.1 スレッドとは?
スレッド(Thread)はプログラムの実行単位です。
プロセス vs スレッド:
- プロセス:独立したリソース(メモリ)を持つ
- スレッド:メモリを共有(軽量)
たとえ話:
- プロセス = 工場
- スレッド = 作業員(同じリソースを使う)
(2) 1.2 なぜマルチスレッドを使う?
| 用途 | 説明 |
|---|---|
| パフォーマンス | 並列処理で高速化 |
| 応答性 | UIスレッドをブロックしない |
| リソース活用 | 待ち時間中もスレッドを活用 |
2. スレッドの作成
(1) 2.1 基本的な使い方
C++11で <thread> ヘッダに std::thread クラスが追加されました。
▶ サンプル 1:マルチスレッドプログラミング(難易度 ⭐)
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(hello); // スレッド作成
t.join(); // 終了待機
std::cout << "Main thread ends" << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用Hello from thread! Main thread ends
(2) 2.2 join vs detach
| 関数 | 機能 | 説明 |
|---|---|---|
join() |
スレッド終了を待機 | スレッド完了までブロック |
detach() |
スレッドを切り離し | バックグラウンドで実行、join不要 |
▶ サンプル 2:joinで待機(難易度 ⭐)
#include <iostream>
#include <thread>
#include <chrono>
void worker(int id) {
for (int i = 0; i < 3; i++) {
std::cout << "Worker " << id << " working..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join(); // t1の終了を待つ
t2.join(); // t2の終了を待つ
std::cout << "All workers done" << std::endl;
return 0;
}
3. スレッドへの引数
(1) 3.1 引数の渡し方
std::thread のコンストラクタは呼び出し可能オブジェクトと可変引数を受け取ります。
▶ サンプル:引数付きスレッド(難易度 ⭐⭐)
#include <iostream>
#include <thread>
#include <string>
void printMessage(std::string msg, int count) {
for (int i = 0; i < count; i++) {
std::cout << msg << std::endl;
}
}
int main() {
std::thread t(printMessage, "Hello", 3);
t.join();
return 0;
}
(2) 3.2 参照渡し
デフォルトでは値渡しです。参照渡しには std::ref を使用します。
▶ サンプル:参照渡し(難易度 ⭐⭐)
#include <iostream>
#include <thread>
#include <functional>
void increment(int& x) {
x++;
}
int main() {
int counter = 0;
std::thread t(increment, std::ref(counter));
t.join();
std::cout << "Counter: " << counter << std::endl; // 出力:1
return 0;
}
4. ミューテックス
(1) 4.1 なぜミューテックスが必要?
問題: 複数スレッドが同時に同じデータにアクセスすると、データ競合(Data Race)が発生します。
▶ サンプル:データ競合(難易度 ⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
int counter = 0;
void increment() {
for (int i = 0; i < 1000; i++) {
counter++; // 競合状態!
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // 10000にならない可能性
return 0;
}
(2) 4.2 ミューテックスの使用
ミューテックス(Mutex)を使用して1つのスレッドのみがアクセスできるようにします。
▶ サンプル:ミューテックス使用(難易度 ⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 1000; i++) {
mtx.lock(); // ロック取得
counter++;
mtx.unlock(); // ロック解放
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // 10000
return 0;
}
(3) 4.3 lock_guard——RAII方式
推奨: std::lock_guard で自動ロック/解放。
void increment() {
for (int i = 0; i < 1000; i++) {
std::lock_guard<std::mutex> lock(mtx); // ロック取得
counter++;
} // スコープ終了で自動解放
}
5. 条件変数
(1) 5.1 なぜ条件変数が必要?
問題: スレッドが特定の条件を待つ必要がある場合(例:キューにデータが入るまで待つ)。
解決策: std::condition_variable
(2) 5.2 例:プロデューサー・コンシューマー(難易度 ⭐⭐⭐)
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
std::queue<int> q;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 0; i < 10; i++) {
std::lock_guard<std::mutex> lock(mtx);
q.push(i);
std::cout << "生産:" << i << std::endl;
cv.notify_one(); // 待機中のスレッドに通知
}
}
void consumer() {
for (int i = 0; i < 10; i++) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !q.empty(); }); // 条件が満たされるまで待機
int value = q.front();
q.pop();
std::cout << "消費:" << value << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
6. 非同期処理
(1) 6.1 std::async
std::async で非同期タスクを開始し、std::future を返します。
▶ サンプル:非同期処理(難易度 ⭐⭐)
#include <iostream>
#include <future>
int calculate(int x) {
return x * x;
}
int main() {
std::future<int> result = std::async(calculate, 10);
std::cout << "結果:" << result.get() << std::endl; // 出力:100
return 0;
}
▶ サンプル 2:複数スレッドでjoin(難易度 ⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
void printNumbers(int start, int end) {
for (int i = start; i <= end; i++) {
std::cout << i << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<std::thread> threads;
// 3つのスレッドを作成
threads.emplace_back(printNumbers, 1, 5);
threads.emplace_back(printNumbers, 6, 10);
threads.emplace_back(printNumbers, 11, 15);
// 全スレッドの完了を待つ
for (auto& t : threads) {
t.join();
}
std::cout << "All threads completed" << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 All threads completed
▶ サンプル 3:ラムダでスレッド(難易度 ⭐⭐)
#include <iostream>
#include <thread>
#include <vector>
int main() {
std::vector<int> results(5);
std::vector<std::thread> threads;
// ラムダでスレッドを作成
for (int i = 0; i < 5; i++) {
threads.emplace_back([i, &results] {
results[i] = i * i;
});
}
// 全スレッドの完了を待つ
for (auto& t : threads) {
t.join();
}
std::cout << "結果: ";
for (int r : results) {
std::cout << r << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用結果: 0 1 4 9 16
❓ よくある質問
Q:スレッドの最大数は? A:CPUコア数やオペレーティングシステムに依存します。
Q:デッドロックとは? A:スレッド同士が待ち合い、どちらも進めなくなる状態です。
回避方法:
- ロック取得順序を統一
std::lock()で複数ミューテックスを一括ロックstd::scoped_lockを使用(C++17)
Q:std::threadとOpenMPの違いは? A: -
std::thread:低レベル、柔軟性が高い
- OpenMP:高レベル、並列化が簡単
📖 まとめ
| トピック | 要点 |
|---|---|
| std::thread | スレッド作成 |
| join/detach | 待機/切り離し |
| std::mutex | ミューテックス、排他制御 |
| std::lock_guard | RAIIで自動ロック |
| std::condition_variable | 条件変数、スレッド間通信 |
| std::async | 非同期タスク |
📝 練習問題
-
初級(難易度 ⭐): 2つのスレッドを作成し、それぞれ「スレッドA」と「スレッドB」を出力し、出力順序を観察してください。
-
中級(難易度 ⭐⭐): 4つのスレッドで配列の値の和を計算(範囲:1-2500、2501-5000...)、結果を合計してください。
-
上級(難易度 ⭐⭐⭐):
std::asyncとstd::futureで並行処理を実装:3つのasyncで異なるファイルを読み込み、結果を待って結合。
std::threadでスレッドを作成、呼び出し可能オブジェクトを渡す- join はスレッドの終了を待つ、detach はスレッドを切り離す
- スレッドでグローバル変数にアクセスするには同期が必要
std::this_thread::sleep_forでスレッドを一時停止- スレッド数はハードウェアに依存(hardware_concurrency)
次のレッスン:マルチスレッド同期(#44)