← SEARCHABLE ARCHIVE
C / C++ · 2026-08-14

C++ 基礎からマスターまで — 第20回

スレッド、atomic、メモリモデルで並行処理を扱う

INTERACTIVE READING MODE →

本ブログの記事は、講義を目的として作成しています。 内容に誤りが含まれる可能性があるため、対象コンパイラと標準ライブラリの資料も確認してください。

複数スレッドが同じメモリにアクセスする問題は、実行順序が変わるだけではありません。C++ memory model では、同期されていない data race は undefined behavior です。

共有不変条件を明示的に守る

class Inventory
{
public:
    void Add(Item item)
    {
        std::lock_guard lock(Mutex);
        Items.push_back(std::move(item));
    }

private:
    std::mutex Mutex;
    std::vector<Item> Items;
};

std::lock_guard は例外を含め scope の終了時に mutex を解放します。ロックは vector の一操作だけでなく、一緒に守るべき不変条件全体を保護します。

複数 mutex を異なる順番で取得すると deadlock が起こり得ます。必要なら std::scoped_lock で同時にロックするか、プロジェクト全体の取得順序を決めます。

Atomic は小さな独立値に向く

std::atomic<unsigned int> completedJobs{0};

void MarkCompleted()
{
    completedJobs.fetch_add(1, std::memory_order_relaxed);
}

他の状態を公開しない統計カウンタなら relaxed ordering で十分な場合があります。ただし一つの atomic は、複数フィールドの整合性を自動で守りません。

既定の sequential consistency は推論しやすい順序を提供します。acquire、release、relaxed は、性能予想ではなく証明された happens-before 関係に基づいて選びます。

jthread で終了を寿命に結び付ける

void Worker(std::stop_token stop)
{
    while (!stop.stop_requested())
        ProcessOneJob();
}

void Run()
{
    std::jthread worker(Worker);
    // デストラクタが停止要求と join を行う。
}

C++20 の std::jthread は破棄時に停止を要求し、終了を待ちます。stop_token は協調的キャンセルなので、応答しない blocking I/O には timeout や別の起床手段が必要です。

まず共有変更状態を減らします。worker に独立した入力を与え、message や queue で結果を集めます。別々の atomic でも同じ cache line にあると false sharing が発生し得ます。

一度テストが通っても安全性の証明にはなりません。所有権、終了条件、ロック順序、公開規則を記録し、ThreadSanitizer と再現可能なストレステストで検証します。