マルチスレッドの基礎
[第]42[課では正規表現について学びました]。
[さて]、[これから]C++の[高度な分野]――マルチスレッド[プログラミング]について取り上げます。
[現代のコンピュータはすべてマルチコアである]が、[シングル]スレッドのプログラムは[1つのコアしか利用できない]ため、[あまりにももったいない]。
マルチスレッド化により、[同時に複数の処理を実行]でき、パフォーマンスが[大幅に向上]します。
1. マルチスレッド[概要]
(1) 1.1 スレッドとは何か?
スレッド(Thread)[は]プログラム[の実行における最小単位]である。
[プロセス] 対 スレッド:
- [プロセス]:[リソース割り当て単位]([独立した]メモリ[領域])
- スレッド:[実行単位]([共有プロセス]メモリ)
[生活]クラス[比]:
- [プロセス] = [工場]
- スレッド = [労働者]([複数の労働者が工場の資源を共有する])
(2) 1.2 [なぜ]マルチスレッドを使うのか?
| [強み] | [説明] |
|---|---|
| [パフォーマンスの向上] | [マルチコア並列計算] |
| [応答性の向上] | UIスレッド[ノンブロッキング] |
| [設計の簡素化] | [異なるタスクを異なる]スレッドに割り当てる |
2. [スレッドの作成]
基本的な使い方
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;
}
[実行結果]:
Hello from thread!
Main thread ends
(2) 2.2 結合と切り離し
| 関数 | [機能] | [説明] |
|---|---|---|
join() |
[スレッドの終了を待機] | [現在のスレッドをブロック] |
detach() |
[分離]スレッド | スレッド[独立実行]、[再]join不可 |
例:[用]join[待機] (難易度 ⭐)
#include iostream
### (2) ▶ 2:(⭐)
#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 参照渡し
[デフォルトでは]、[引数は] pass-by-value です。[参照渡し] を使用するには、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; // output: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::vectorstd::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つのスレッドのみが]共有データにアクセスできるようにするために使用されます。
例:[mutex]による[保護] (難易度 ⭐⭐)
#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::vectorstd::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_guardstd::mutex lock(mtx); //,
counter++;
} // ...
}
5. 状態変数
(1) 5.1 なぜ条件変数が必要なのか?
[問題]: スレッドは[特定の]condition[が成立する]ことを[待つ必要がある]([例えば、キューが空でない場合])。
[解決策]: std::condition_variable
(2) 5.2 例:[プロデューサー]-[コンシューマー] (難易度 ⭐⭐⭐)
#include iostream
#include thread
#include queue
#include mutex
#include condition_variable
std::queueint q;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 0; i < 10; i++) {
std::lock_guardstd::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_lockstd::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::futureint result = std::async(calculate, 10);
std::cout << ":" << result.get() << std::endl; // output:100
return 0;
}
❓ よくある質問
Q:[どのくらいの]スレッド数が[適切]ですか? A:[通常は]CPUの[コア数]と同じです。[多すぎるとコンテキストスイッチのオーバーヘッドが発生します]。
Q:デッドロックとは何ですか? A:[2つの]スレッドが[互いに相手がロックを解放するのを待ち続ける]ことで、[その結果]、[どちらも処理を続行できなくなる]状態のことです。
[回避策]:
- [固定順序でロック]
- [使用]
std::lock()[複数のミューテックスを同時にロックする] - [用]
std::scoped_lock(C++17)
std::thread:[柔軟性]、[クロスプラットフォーム] - OpenMP:[シンプル]、[科学計算に適している]📖 まとめ
| [知識ポイント] | [要点] |
|---|---|
| std::thread | [スレッドの作成] |
| 参加/離脱 | [待機]/[分離]スレッド |
| std::mutex | [ミューテックス],[共有データの保護] |
| std::lock_guard | RAII[ロック方式] |
| std::condition_variable | コンディション変数、スレッド間通信 |
| std::async | [非同期タスク] |
📝 練習問題
-
**初心者(難易度 ⭐):[2つの]スレッドを作成し、[それぞれ]「threadA」[と]「threadB」を出力し、[出力の順序がランダムになることを]確認する。
-
**中級(難易度 ⭐⭐):[4つの]スレッドを[作成]し、[各]スレッドで[ある範囲の数値]の[合計]を[計算]し([例] 1~2500、2501~5000...)、[最後に合計結果をまとめ]ます。
-
**上級(難易度 ⭐⭐⭐):[std::async] [と] [std::future] を使用して [並列ダウンロードシミュレータを実装する]:[3つの] [非同期タスクを作成し]、[それぞれで異なるサイズの] ファイルを [ダウンロードをシミュレートし]、[すべて完了するのを待ってから結果をまとめる]。
- std::thread [スレッドの作成]、[オブジェクトの引数指定]
- [待機]スレッド[終了]に参加する、[分離]スレッドから切り離す
- スレッド間で共有されるグローバル変数には同期処理が必要
- std::this_thread::sleep_for [スレッドを]休眠させる
- スレッド[数はハードウェアがサポートする上限を超えないようにすること](hardware_concurrency)
[次のレッスン]:マルチスレッドの同期(#44)



