404 Not Found

404 Not Found


nginx

マルチスレッドの基礎

[第]42[課では正規表現について学びました]。

[さて]、[これから]C++の[高度な分野]――マルチスレッド[プログラミング]について取り上げます。

[現代のコンピュータはすべてマルチコアである]が、[シングル]スレッドのプログラムは[1つのコアしか利用できない]ため、[あまりにももったいない]。

マルチスレッド化により、[同時に複数の処理を実行]でき、パフォーマンスが[大幅に向上]します。


1. マルチスレッド[概要]

(1) 1.1 スレッドとは何か?

スレッド(Thread)[は]プログラム[の実行における最小単位]である。

[プロセス] 対 スレッド:

[生活]クラス[比]:


(2) 1.2 [なぜ]マルチスレッドを使うのか?

[強み] [説明]
[パフォーマンスの向上] [マルチコア並列計算]
[応答性の向上] UIスレッド[ノンブロッキング]
[設計の簡素化] [異なるタスクを異なる]スレッドに割り当てる


2. [スレッドの作成]

基本的な使い方

C++11のthreadヘッダーファイルには、std::threadクラスが[提供されています]。

例:[スレッドの作成] (難易度 ⭐)

▶ サンプル 1: マルチスレッド[プログラミングデモ] (難易度 ⭐)

CPP
#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() [スレッドの終了を待機] [現在のスレッドをブロック]
detach() [分離]スレッド スレッド[独立実行]、[再]join不可

例:[用]join[待機] (難易度 ⭐)

CPP
#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 コンストラクタは、[任意の呼び出し可能な]オブジェクト[および][引数]を受け入れることができます。

例:[引数の渡し方] (難易度 ⭐⭐)

CPP
#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 を使用する必要があります。

例:[用]参照渡し (難易度 ⭐⭐)

CPP
#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)が[発生する]。

例:[データ競合] (難易度 ⭐⭐)

CPP
#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]による[保護] (難易度 ⭐⭐)

CPP
#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 [自動ロック]/[ロック解除]。

CPP
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 例:[プロデューサー]-[コンシューマー] (難易度 ⭐⭐⭐)

CPP
#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を[返す]。

例:[非同期計算] (難易度 ⭐⭐)

CPP
#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つの]スレッドが[互いに相手がロックを解放するのを待ち続ける]ことで、[その結果]、[どちらも処理を続行できなくなる]状態のことです。

[回避策]:


Q std::threadとOpenMP、どちらが良いですか?
A - std::thread:[柔軟性]、[クロスプラットフォーム] - OpenMP:[シンプル]、[科学計算に適している]

📖 まとめ

[知識ポイント] [要点]
std::thread [スレッドの作成]
参加/離脱 [待機]/[分離]スレッド
std::mutex [ミューテックス],[共有データの保護]
std::lock_guard RAII[ロック方式]
std::condition_variable コンディション変数、スレッド間通信
std::async [非同期タスク]

📝 練習問題

  1. **初心者(難易度 ⭐):[2つの]スレッドを作成し、[それぞれ]「threadA」[と]「threadB」を出力し、[出力の順序がランダムになることを]確認する。

  2. **中級(難易度 ⭐⭐):[4つの]スレッドを[作成]し、[各]スレッドで[ある範囲の数値]の[合計]を[計算]し([例] 1~2500、2501~5000...)、[最後に合計結果をまとめ]ます。

  3. **上級(難易度 ⭐⭐⭐):[std::async] [と] [std::future] を使用して [並列ダウンロードシミュレータを実装する]:[3つの] [非同期タスクを作成し]、[それぞれで異なるサイズの] ファイルを [ダウンロードをシミュレートし]、[すべて完了するのを待ってから結果をまとめる]。



[次のレッスン]:マルチスレッドの同期(#44)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%