std::thread
『C++』では <thread> ヘッダに含まれる std::thread クラスを使ってスレッドを生成できます。スレッドを使うと複数の処理を並行して実行できるため、重い計算や I/O 待ちの間に別の処理を進めることが可能です。スレッド間でデータを共有する場合は std::mutex や std::lock_guard で排他制御を行い、データ競合を防いでください。
構文
// ========================================
// std::thread の基本構文
// ========================================
#include <thread>
#include <mutex>
// ----------------------------------------
// スレッドの生成と join
// ----------------------------------------
// 関数をスレッドとして起動します
void worker() { /* 処理 */ }
std::thread t(worker); // スレッド起動
t.join(); // 終了を待機します(呼び出し元がブロックします)
// ラムダ式でスレッドを起動します(C++11 以降)
std::thread t2([]() { /* 処理 */ });
t2.join();
// 引数を渡してスレッドを起動します
void workerWithArg(int id, std::string name) { /* 処理 */ }
std::thread t3(workerWithArg, 1, "Okabe");
t3.join();
// ----------------------------------------
// detach:スレッドを切り離して独立させます
// ----------------------------------------
std::thread t4(worker);
t4.detach(); // join しない場合は必ず detach が必要です
// ----------------------------------------
// mutex で排他制御します
// ----------------------------------------
std::mutex mtx;
void safeWorker() {
std::lock_guard<std::mutex> lock(mtx); // スコープを抜けると自動解放します
// クリティカルセクション(共有データへのアクセス)
}
// ----------------------------------------
// スレッドの識別
// ----------------------------------------
std::thread::id tid = t.get_id(); // スレッド ID を取得します
// std::this_thread::get_id() // 現在のスレッド ID を取得します
構文一覧
| 構文・クラス | 概要 |
|---|---|
| std::thread t(f) | 関数 f を新しいスレッドで起動します。引数は追加で渡せます。 |
| t.join() | スレッド t の終了を待機します。呼び出し元スレッドはブロックされます。join も detach もせずにデストラクタが呼ばれると std::terminate になります。 |
| t.detach() | スレッドをバックグラウンドで独立して動作させます。以降は join できなくなります。 |
| t.joinable() | スレッドが join または detach 可能な状態かどうかを返します。 |
| std::mutex | 相互排他ロックです。lock() / unlock() で排他制御します。 |
| std::lock_guard | コンストラクタで mutex をロックし、デストラクタで自動解放する RAII ラッパーです。 |
| std::unique_lock | lock_guard より柔軟な mutex ラッパーです。途中でロック解放・再取得ができます。 |
| std::this_thread::sleep_for() | 現在のスレッドを指定時間スリープさせます。 |
| std::this_thread::get_id() | 現在のスレッドの ID を取得します。 |
| hardware_concurrency() | 論理コア数を返します。スレッド数の参考値として使えます。 |
サンプルコード
sg_threads.cpp
// ========================================
// sg_threads.cpp
// Steins;Gate のラボメンがそれぞれ並行して
// タイムマシン開発タスクを担当するサンプルです
// std::thread / std::mutex / std::lock_guard を使っています
// ========================================
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <string>
#include <chrono>
// ========================================
// 共有ログバッファとその保護用 mutex です
// 複数スレッドが同時に cout へ書き込むと
// 出力が混在するため mutex で保護します
// ========================================
std::mutex logMtx;
void log(const std::string& member, const std::string& task) {
// lock_guard はスコープを抜けると自動的にロックを解放します
std::lock_guard<std::mutex> lock(logMtx);
std::cout << "[" << member << "] " << task << std::endl;
}
// ========================================
// 岡部倫太郎: IBN 5100 の解析を担当します
// ========================================
void okabe(int stepCount) {
log("岡部倫太郎", "IBN 5100 の解析を開始します...");
for (int i = 1; i <= stepCount; i++) {
// 解析中の重い処理をスリープで表現します
std::this_thread::sleep_for(std::chrono::milliseconds(120));
log("岡部倫太郎", "解析ステップ " + std::to_string(i) + " 完了。エル・プサイ・コングルゥ");
}
log("岡部倫太郎", "IBN 5100 解析完了。これが運命石の扉の選択か。");
}
// ========================================
// 牧瀬紅莉栖: タイムリープマシンの理論構築を担当します
// ========================================
void makise(int stepCount) {
log("牧瀬紅莉栖", "タイムリープマシンの理論構築を開始します...");
for (int i = 1; i <= stepCount; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(80));
log("牧瀬紅莉栖", "理論構築ステップ " + std::to_string(i) + " 完了。記憶の転送メカニズムを解析中です。");
}
log("牧瀬紅莉栖", "タイムリープマシン理論構築完了。あなたがいなければ到達できなかった結論です。");
}
// ========================================
// 椎名まゆり: 補給物資(オペレーション・ツールズ)の管理を担当します
// ========================================
void mayuri(int stepCount) {
log("椎名まゆり", "補給物資の管理を開始するよ~!");
for (int i = 1; i <= stepCount; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
log("椎名まゆり", "補給チェック " + std::to_string(i) + " 完了。クリスティーナにポッキー届けておいたよ!");
}
log("椎名まゆり", "補給物資の管理完了。まゆしぃ、がんばったよ~!");
}
// ========================================
// 阿万音鈴羽: タイムマシン本体の組み立てを担当します
// ========================================
void suzuha(int stepCount) {
log("阿万音鈴羽", "タイムマシン本体の組み立てを開始します。");
for (int i = 1; i <= stepCount; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(150));
log("阿万音鈴羽", "組み立てステップ " + std::to_string(i) + " 完了。2036 年の未来を変えるために急ぎます。");
}
log("阿万音鈴羽", "タイムマシン本体の組み立て完了。あとは燃料だけです。");
}
// ========================================
// 橋田至: ダイバージェンスメーターの検証を担当します
// ========================================
void daru(int stepCount) {
log("橋田至", "ダイバージェンスメーターの検証を開始します(萌えコード全開で)。");
for (int i = 1; i <= stepCount; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
log("橋田至", "検証ステップ " + std::to_string(i) + " 完了。ハッキングスキルの真髄をお見せしますよ。");
}
log("橋田至", "ダイバージェンスメーター検証完了。世界線変動率 1.048596 を確認しました。");
}
// ========================================
// メイン: 各ラボメンをスレッドとして起動します
// ========================================
int main() {
std::cout << "=== 未来ガジェット研究所 タイムマシン開発プロジェクト ===" << std::endl;
std::cout << "論理コア数: " << std::thread::hardware_concurrency() << std::endl;
std::cout << std::endl;
// 各タスクのステップ数です
const int STEPS = 3;
// ラボメンそれぞれをスレッドとして起動します
std::thread t_okabe(okabe, STEPS);
std::thread t_makise(makise, STEPS);
std::thread t_mayuri(mayuri, STEPS);
std::thread t_suzuha(suzuha, STEPS);
std::thread t_daru(daru, STEPS);
// 全スレッドの終了を待機します
// join() を呼ばないと std::terminate が発生します
t_okabe.join();
t_makise.join();
t_mayuri.join();
t_suzuha.join();
t_daru.join();
std::cout << std::endl;
std::cout << "=== 全タスク完了。オペレーション・スタインズゲート、発動準備完了です ===" << std::endl;
return 0;
}
# コンパイルします(-lpthread でスレッドライブラリをリンクします) g++ -std=c++11 sg_threads.cpp -o sg_threads -lpthread && ./sg_threads === 未来ガジェット研究所 タイムマシン開発プロジェクト === 論理コア数: 8 [岡部倫太郎] IBN 5100 の解析を開始します... [牧瀬紅莉栖] タイムリープマシンの理論構築を開始します... [椎名まゆり] 補給物資の管理を開始するよ~! [阿万音鈴羽] タイムマシン本体の組み立てを開始します。 [橋田至] ダイバージェンスメーターの検証を開始します(萌えコード全開で)。 [牧瀬紅莉栖] 理論構築ステップ 1 完了。記憶の転送メカニズムを解析中です。 [橋田至] 検証ステップ 1 完了。ハッキングスキルの真髄をお見せしますよ。 [岡部倫太郎] 解析ステップ 1 完了。エル・プサイ・コングルゥ [阿万音鈴羽] 組み立てステップ 1 完了。2036 年の未来を変えるために急ぎます。 [牧瀬紅莉栖] 理論構築ステップ 2 完了。記憶の転送メカニズムを解析中です。 [橋田至] 検証ステップ 2 完了。ハッキングスキルの真髄をお見せしますよ。 [岡部倫太郎] 解析ステップ 2 完了。エル・プサイ・コングルゥ [牧瀬紅莉栖] 理論構築ステップ 3 完了。記憶の転送メカニズムを解析中です。 [牧瀬紅莉栖] タイムリープマシン理論構築完了。あなたがいなければ到達できなかった結論です。 [阿万音鈴羽] 組み立てステップ 2 完了。2036 年の未来を変えるために急ぎます。 [橋田至] 検証ステップ 3 完了。ハッキングスキルの真髄をお見せしますよ。 [橋田至] ダイバージェンスメーター検証完了。世界線変動率 1.048596 を確認しました。 [岡部倫太郎] 解析ステップ 3 完了。エル・プサイ・コングルゥ [岡部倫太郎] IBN 5100 解析完了。これが運命石の扉の選択か。 [阿万音鈴羽] 組み立てステップ 3 完了。2036 年の未来を変えるために急ぎます。 [阿万音鈴羽] タイムマシン本体の組み立て完了。あとは燃料だけです。 [椎名まゆり] 補給チェック 1 完了。クリスティーナにポッキー届けておいたよ! [椎名まゆり] 補給チェック 2 完了。クリスティーナにポッキー届けておいたよ! [椎名まゆり] 補給チェック 3 完了。クリスティーナにポッキー届けておいたよ! [椎名まゆり] 補給物資の管理完了。まゆしぃ、がんばったよ~! === 全タスク完了。オペレーション・スタインズゲート、発動準備完了です ===
よくあるミス
スレッドプログラミングでは、join() の呼び忘れとデータ競合が特によく発生するミスです。
join() 忘れでプログラムが異常終了する
std::thread オブジェクトのデストラクタは、スレッドがまだ joinable(join も detach もしていない)の状態だと std::terminate() を呼び出してプログラムを強制終了させます。関数の途中で return したり例外が投げられたりした場合でも、join を確実に呼ぶように注意が必要です。
NG
#include <iostream>
#include <thread>
#include <stdexcept>
void worker() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::cout << "橋田至: 作業完了" << std::endl;
}
// NG: join() を呼び忘れると std::terminate() でクラッシュします
void badExample() {
std::thread t(worker);
// 途中で例外が発生するか早期 return する場合、
// t.join() が呼ばれずデストラクタで terminate します
throw std::runtime_error("エラー発生");
t.join(); // ここには到達しません
}
OK
// OK: join() を確実に呼ぶには try-catch か RAII ラッパーを使います
void goodExample() {
std::thread t(worker);
try {
// 何らかの処理をします
} catch (...) {
t.join(); // 例外発生時も join します
throw;
}
t.join(); // 正常終了時も join します
}
データ競合による未定義動作
複数のスレッドが同じ変数を排他制御なしに読み書きすると、データ競合(data race)が発生します。データ競合は C++ では未定義動作であり、クラッシュ・不正な値・間欠的なバグなど様々な症状として現れます。std::mutex と std::lock_guard で必ず保護してください。
NG: mutex なし(データ競合)
#include <iostream>
#include <thread>
#include <mutex>
int sharedCounter = 0; // 共有変数です
std::mutex counterMtx;
// NG: mutex なしに複数スレッドから読み書きするとデータ競合が発生します
void badIncrement() {
for (int i = 0; i < 1000; i++) {
sharedCounter++; // 読み取り→インクリメント→書き込みが非アトミックです
}
}
int main() {
// NG の例(結果が 2000 にならないことがある)
sharedCounter = 0;
std::thread t1(badIncrement);
std::thread t2(badIncrement);
t1.join();
t2.join();
std::cout << "NG: " << sharedCounter << "(期待値: 2000)" << std::endl;
return 0;
}
OK: mutex で保護(安全)
// OK: mutex で保護すると安全にインクリメントできます
void safeIncrement() {
for (int i = 0; i < 1000; i++) {
std::lock_guard<std::mutex> lock(counterMtx);
sharedCounter++; // ロック中は他のスレッドはここに入れません
}
}
int main() {
// OK の例(必ず 2000 になります)
sharedCounter = 0;
std::thread t3(safeIncrement);
std::thread t4(safeIncrement);
t3.join();
t4.join();
std::cout << "OK: " << sharedCounter << "(期待値: 2000)" << std::endl;
return 0;
}
概要
std::thread は C++11 で標準化されたスレッドクラスです。コンストラクタに関数(または呼び出し可能オブジェクト)と引数を渡すと即座にスレッドが起動します。起動したスレッドは join() で終了を待機するか、detach() でバックグラウンド動作に切り離す必要があります。どちらも呼ばずにスレッドオブジェクトのデストラクタが実行されると std::terminate でプログラムが強制終了します。スレッド間で共有データを扱う場合は必ず std::mutex で排他制御してください。std::lock_guard を使うと RAII によりスコープを抜けた時点でロックが自動解放されるため、例外安全なコードを書けます。コンパイル時は -lpthread オプション(Linux 環境)または -pthread オプションでスレッドライブラリをリンクしてください。スレッド数の目安は std::thread::hardware_concurrency() で取得できる論理コア数が参考になります。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。