モダン第5章【C++11/14】暙準マルチスレッドず䞊行凊理

std::thread・排他制埡mutex/lock_guard・デヌタ競合の撲滅ずstd::atomic

珟代のすべおのCPUはマルチコア8コア、16コアが圓たり前であり、1本のメむンスレッドだけでゲヌムや高負荷システムを動かすのはCPUリ゜ヌスの倧半をドブに捚おるようなものです。しかし、か぀おのC++03時代は蚀語仕様にスレッドの抂念が存圚せず、OS䟝存の泥臭いAPIPOSIX pthreadsやWin32 APIを盎接叩かなければなりたせんでした。C++11で初めお蚀語仕様ずしお組み蟌たれた【暙準スレッドラむブラリstd::thread】ず【メモリモデル】は、クロスプラットフォヌムで安党・高速な䞊行凊理ぞの扉を開きたした。本章では、非同期タスクの起動・埅機join / detachから、未定矩動䜜を匕き起こす「デヌタ競合Data Race」のメカニズム、RAIIによる排他制埡std::mutex, std::lock_guard、そしお極限の䜎オヌバヌヘッドを実珟する「ロックフリヌ原子的操䜜std::atomic」たでを培底解説したす。

← 前の章ぞ進む
次の章ぞ進む →

M5.1 OS䟝存スレッドAPIの終焉C++11暙準メモリモデルの革呜

pthreadやWin32から脱华し、なぜ蚀語仕様レベルでスレッドが定矩される必芁があったのかを解説したす。

🐧 ペンギン先茩
シロクマ先生ステヌゞ切り替え時に裏でBGMや敵テクスチャを非同期ロヌドしようずしたら、Windowsでは CreateThread、MacやLinuxでは pthread_create を曞かねばならず、#ifdef 地獄でコヌドが爆発したした 
🐻‍❄ シロクマ先生
ふぉっふぉっふぉC++03たでは「䞖界はシングルスレッドで動くもの」ずしお芏栌が䜜られおおったからのう。マルチスレッドはすべおOS独自の拡匵機胜に頌るしかなかったのじゃ。
🐧 ペンギン先茩
C++11で暙準ラむブラリに std::thread が入っお、䜕が倉わったんですか
🐻‍❄ シロクマ先生
OSの差分が完党に消滅しただけでなく、蚀語芏栌ずしお【メモリモデル倉数ぞの読み曞きの順序保蚌】が厳栌に定矩されたのじゃどのOS・どのCPUx86/ARMでも、決定論的で安党な䞊行凊理が曞けるようになったのじゃぞ

M5.2 デヌタ競合Data Raceの恐怖ず std::lock_guard によるRAII排他制埡

耇数のスレッドが同時に同じ倉数を曎新した瞬間に起きる砎滅ず、デッドロックを防ぐRAIIロックの仕組みを孊びたす。

🐧 ペンギン先茩
先生2぀のスレッドからスコア倉数 g_score にそれぞれ 1000回ず぀ +1 加算したのに、蚈算結果が 2000 ではなく 1432 ずか倉な数字になりたす
🐻‍❄ シロクマ先生
それこそが䞊行凊理最倧の魔物【デヌタ競合Data Race / レヌスコンディション】じゃg_score++ は1呜什ではなく、「CPUレゞスタに読み蟌む」「加算する」「メモリに曞き戻す」ずいう3ステップじゃから、途䞭で別スレッドに割り蟌たれお倀が䞊曞き消滅したのじゃ

M5.3 実装線std::atomic によるロックフリヌ高速カりンタずスレッド凊理

重いミュヌテックスを回避し、CPUのハヌドりェアアトミック呜什で超高速に動䜜するカりンタを実装したす。

📄 ThreadSafeCounter.cpp
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <atomic>

// 1. ミュヌテックスによる排他制埡
int g_mutexCounter = 0;
std::mutex g_mtx;

void incrementWithMutex(int count) {
    for (int i = 0; i < count; ++i) {
        std::lock_guard<std::mutex> lock(g_mtx); // RAIIロック
        g_mutexCounter++;
    }
}

// 2. std::atomic によるロックフリヌ原子的加算CPU呜什レベルの排他
std::atomic<int> g_atomicCounter(0);

void incrementWithAtomic(int count) {
    for (int i = 0; i < count; ++i) {
        g_atomicCounter.fetch_add(1); // たたは単に g_atomicCounter++
    }
}

int main() {
    const int THREAD_COUNT = 4;
    const int ITERATIONS = 10000;

    std::cout << "=== 1. std::mutex による䞊行凊理 ===" << std::endl;
    std::vector<std::thread> threads1;
    for (int i = 0; i < THREAD_COUNT; ++i) {
        threads1.push_back(std::thread(incrementWithMutex, ITERATIONS));
    }
    for (size_t i = 0; i < threads1.size(); ++i) {
        threads1[i].join(); // 党スレッド終了埅機
    }
    std::cout << "Mutex結果: " << g_mutexCounter << " (期埅倀: " << (THREAD_COUNT * ITERATIONS) << ")" << std::endl;

    std::cout << "\n=== 2. std::atomic によるロックフリヌ凊理 ===" << std::endl;
    std::vector<std::thread> threads2;
    for (int i = 0; i < THREAD_COUNT; ++i) {
        threads2.push_back(std::thread(incrementWithAtomic, ITERATIONS));
    }
    for (size_t i = 0; i < threads2.size(); ++i) {
        threads2[i].join();
    }
    std::cout << "Atomic結果: " << g_atomicCounter.load() << " (期埅倀: " << (THREAD_COUNT * ITERATIONS) << ")" << std::endl;

    return 0;
}
💡 このセクションの重芁ポむント
  • C++11暙準スレッドの恩恵: OS䟝存コヌドを完党に排陀し、std::thread ずラムダ匏で型安党・ポヌタブルな䞊行凊理を実珟する。
  • デヌタ競合は未定矩動䜜: 耇数スレッドが同期なしで共有倉数を曞き換えるずメモリが砎損する。必ず mutex か atomic を介さねばならない。
  • RAIIロックの培底: 生 mutex.lock() は䜿わず、垞に std::lock_guard たたは std::unique_lock を䜿甚しお䟋倖発生時のアンロック挏れを防ぐ。
  • std::atomic の嚁力: 単䞀敎数のむンクリメントやフラグ曎新には、OSのコンテキストスむッチを䌎わないロックフリヌ原子的操䜜std::atomicが最速。

📝 理解床チェッククむズ

Q1. C++11の `std::thread` オブゞェクトを砎棄する際、join() も detach() も呌ばれおいない堎合に発生する重倧な動䜜はどれですか
  • 1. 裏で勝手にスレッドが終了するたで静かに埅機する
  • 2. std::terminate() が匷制的に呌び出され、プログラム党䜓が即座に異垞終了クラッシュする
  • 3. コンパむル゚ラヌになる
  • 4. スレッドが自動的に detach されおOSに匕き枡される
正解ず解説を芋る
正解std::terminate() が匷制的に呌び出され、プログラム党䜓が即座に異垞終了クラッシュする
C++芏栌では、joinable な状態join も detach もされおいないの std::thread のデストラクタが走るず、安党のため問答無甚で std::terminate() が呌ばれ、プロセスが即時アボヌトしたす。
Q2. ミュヌテックスのロックに `std::lock_guard<std::mutex>` を䜿甚する最倧のメリットは䜕ですか
  • 1. ロックの凊理速床が10倍速くなるため
  • 2. スコヌプを抜けた際や途䞭で䟋倖がスロヌされた堎合でも、デストラクタにより確実に unlock() が呌び出されデッドロックを防ぐため
  • 3. 耇数のミュヌテックスを1぀の倉数で同時に操䜜できるため
  • 4. メモリ䜿甚量がれロになるため
正解ず解説を芋る
正解スコヌプを抜けた際や途䞭で䟋倖がスロヌされた堎合でも、デストラクタにより確実に unlock() が呌び出されデッドロックを防ぐため
std::lock_guard は RAIIResource Acquisition Is Initializationの兞型䟋であり、構築時に lock() し砎棄時に必ず unlock() を呌び出すため、関数の途䞭で return したり䟋倖が飛んでもアンロック挏れが発生したせん。
Q3. 敎数カりンタの䞊行加算においお、`std::mutex` ではなく `std::atomic<int>` を遞ぶ䞻な利点は䜕ですか
  • 1. atomic を䜿うずカりンタが無限倧たで扱えるようになるため
  • 2. OSのカヌネル呌び出しやスレッド䌑止コンテキストスむッチを䌎わず、CPUの原子的ハヌドりェア呜什で超高速に加算できるため
  • 3. atomic はC++03でも利甚可胜なため
  • 4. atomic 倉数はスタックメモリを䞀切消費しないため
正解ず解説を芋る
正解OSのカヌネル呌び出しやスレッド䌑止コンテキストスむッチを䌎わず、CPUの原子的ハヌドりェア呜什で超高速に加算できるため
std::atomic はCPUのロックプレフィックス呜什LOCK XADDなどに盎接コンパむルされるため、ミュヌテックスのような重いOSカヌネル同期やスレッドのブロック・りェむクアップのオヌバヌヘッドなしに最高速で動䜜したす。
← 前の章ぞ進む
次の章ぞ進む →

🐻‍❄ シロクマC++ラボ むンタラクティブ孊習システム

ブラりザでJavaScriptを有効にするず、ブラりザ内むンベヌダヌゲヌム゚ミュレヌタ、メモリマップ可芖化、UMLクラス図、察話型解説、理解床クむズが起動したす。

むンタラクティブ孊習を開始する →