本文へスキップ
BecomeCoder

C++コース · 第19章 並行処理とC++20 · レッスン74

並行処理 ― std::thread・mutex・async/future

ローカル実施

導入

重い計算や通信待ちを1つずつ順番にこなしていると、CPUが複数コアあっても使い切れません。C++には複数の処理を同時に走らせる「並行処理(マルチスレッド)」の標準機能があり、std::thread で別スレッドを起こせます。

この回は読み物です。マルチスレッドの挙動はブラウザの実行環境では不安定なため、手元の環境(g++ / clang など)で確かめてください。

説明

std::thread関数を渡すと、その関数が別スレッドで動き出します。t.join() で、そのスレッドが終わるのを待ちます(join() を呼ばずにスレッドを放置するとプログラムが異常終了するので必須です)。

#include <iostream>
#include <thread>
#include <string>
using namespace std;

void greet(const string& name) {
    cout << "こんにちは、" << name << endl;
}

int main() {
    thread t(greet, "たろう");   // greet を別スレッドで実行開始
    t.join();                    // 終わるのを待つ
    cout << "メイン処理終了" << endl;
    return 0;
}

複数のスレッドが同じ変数を同時に読み書きすると、「データ競合」が起きて結果が不定になります。これを防ぐのが std::mutex(排他制御用のロック)です。第16章の RAII と同じ考え方で、std::lock_guard を使うと、スコープを抜けるときに自動でロックが解放されます。

sequenceDiagram
  participant T1 as スレッド1
  participant M as mutex
  participant T2 as スレッド2
  T1->>M: lock_guard でロック取得
  T2->>M: ロックを取ろうとして待たされる
  T1->>M: スコープを抜けて自動解放
  M->>T2: ロック取得できる
  T2->>M: スコープを抜けて自動解放
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int counter = 0;
mutex mtx;   // 排他制御用のロック

void increment() {
    for (int i = 0; i < 1000; i++) {
        lock_guard<mutex> lock(mtx);   // RAII でロック取得・自動解放
        counter++;
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << counter << endl;   // 2000(mutex がないとずれることがある)
    return 0;
}

「結果だけ後で受け取りたい」場合は、std::thread を直接使うより std::async が便利です。裏で非同期に処理を実行し、std::future.get() で結果を待って受け取ります。

#include <iostream>
#include <future>
using namespace std;

int heavyCalc(int n) {
    return n * n;
}

int main() {
    future<int> result = async(heavyCalc, 12);   // 裏で非同期に実行される
    // ここで他の処理を並行して進めることもできる
    cout << result.get() << endl;   // .get() で結果を待って受け取る(144)
    return 0;
}

まとめ

std::thread は関数を別スレッドで動かし、join() で終了を待ちます。複数スレッドが同じデータを触るなら std::mutex で排他制御し、std::lock_guard(RAII)でロックの解放漏れを防ぎます。結果だけ後で受け取りたいなら std::asyncstd::future::get() が簡潔です。