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:マルチスレッドプログラミング(難易度 ⭐)

TEXT 📖 参照専用
#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で待機(難易度 ⭐)

CPP
#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 のコンストラクタは呼び出し可能オブジェクト可変引数を受け取ります。

▶ サンプル:引数付きスレッド(難易度 ⭐⭐)

TEXT 📖 参照専用
#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 を使用します。

▶ サンプル:参照渡し(難易度 ⭐⭐)

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; // 出力:1
    return 0;
}
▶ 試してみよう


4. ミューテックス

(1) 4.1 なぜミューテックスが必要?

問題: 複数スレッドが同時に同じデータにアクセスすると、データ競合(Data Race)が発生します。

▶ サンプル:データ競合(難易度 ⭐⭐)

TEXT 📖 参照専用
#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つのスレッドのみがアクセスできるようにします。

▶ サンプル:ミューテックス使用(難易度 ⭐⭐)

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::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 で自動ロック/解放。

TEXT 📖 参照専用
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 例:プロデューサー・コンシューマー(難易度 ⭐⭐⭐)

CPP
#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 を返します。

▶ サンプル:非同期処理(難易度 ⭐⭐)

TEXT 📖 参照専用
#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(難易度 ⭐⭐)

CPP
#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:ラムダでスレッド(難易度 ⭐⭐)

CPP
#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:スレッド同士が待ち合い、どちらも進めなくなる状態です。

回避方法:


Q:std::threadとOpenMPの違いは? A: - std::thread:低レベル、柔軟性が高い

  • OpenMP:高レベル、並列化が簡単

📖 まとめ

トピック 要点
std::thread スレッド作成
join/detach 待機/切り離し
std::mutex ミューテックス、排他制御
std::lock_guard RAIIで自動ロック
std::condition_variable 条件変数、スレッド間通信
std::async 非同期タスク

📝 練習問題

  1. 初級(難易度 ⭐): 2つのスレッドを作成し、それぞれ「スレッドA」と「スレッドB」を出力し、出力順序を観察してください。

  2. 中級(難易度 ⭐⭐): 4つのスレッドで配列の値の和を計算(範囲:1-2500、2501-5000...)、結果を合計してください。

  3. 上級(難易度 ⭐⭐⭐): std::asyncstd::future で並行処理を実装:3つのasyncで異なるファイルを読み込み、結果を待って結合。



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

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%