最終更新日時(UTC): 2025年07月10日 01時26分30秒
Koichi Murase が更新

履歴 編集

# ブロックスコープを持つstatic変数初期化のスレッドセーフ化 [N2660](C++11)

このページはC++11に採用された言語機能の変更を解説しています。

のちのC++規格でさらに変更される場合があるため関連項目を参照してください。

## 概要

ブロックスコープを持つ`static`変数の初期化は、スレッドセーフであることが規定された。

`static`変数の初期化が完了するまで、他のスレッドは初期化処理の前で待機する。

    class singleton {
    public:
      static singleton& get_instance()
      {
        static singleton instance; // この初期化はスレッドセーフ
        return instance;
      }
    };

## 仕様

### staticローカル変数の初期化

- ローカルの`static`変数を宣言と同時に初期化した場合、並行実行は初期化が完了するまで待機しなければならない

### 非ローカルなstatic変数の初期化

- 非ローカルな`static`変数の初期化は、`main()`関数が開始する前に順不同で行われること

### static変数のデストラクタ

- `static`変数のデストラクタは、全てのスレッドが終了したあとに、宣言と逆の順番にデストラクタが呼び出され、破棄される

## 例

    #include <thread>
    #include <vector>
    #include <cassert>

    class singleton {
      int value_ = 3;
    public:
      static singleton& get_instance()
      {
        static singleton instance; // この初期化はスレッドセーフ
        return instance;
      }

      int get() const
      { return value_; }
    };

    int main()
    {
      // 4スレッドで並行に、
      // singletonのstatic変数へとアクセスする
      std::vector<std::thread> threads;
      for (int i = 0; i < 4; ++i) {
        std::thread t([] {
          // singletonのメンバ変数value_が
          // 常に初期化された状態であることを確認
          assert(singleton::get_instance().get() == 3);
        });
        threads.push_back(std::move(t));
      }

      for (std::thread& t : threads) {
        t.join();
      }
    }

### 出力

## この機能が必要になった背景・経緯

ローカルの`static`変数を初期化することがスレッドセーフではなかった前バージョンまでは、同じことをするために、Double Checked Lockingという技法によって、ユーザーが初期化をスレッドセーフにしていた。それは、以下のようなものである：

1. `static`ローカル変数の代わりに、ポインタを`static`メンバ変数として持つ
2. ポインタがヌルかどうかをチェックし、ヌルであればロックを取得する
3. ヌルチェックとロック取得の間に有効なポインタになる可能性があるためにロック取得後にさらにヌルチェックをし、ヌルであれば初期化をする

こういった技法は非常に難しく、潜在的な並行性バグの可能性があるためにユーザーの行動を制限していた。

そのために、言語によって`static`変数の初期化がスレッドセーフであることを保証することとなった。

## 参照

- N2148 Dynamic Initialization and Destruction with Concurrency
- N2325 Dynamic Initialization and Destruction with Concurrency
- N2382 Dynamic Initialization and Destruction with Concurrency
- N2444 Dynamic Initialization and Destruction with Concurrency
- N2513 Dynamic Initialization and Destruction with Concurrency
- N2660 Dynamic Initialization and Destruction with Concurrency
- C++0x時代の Double-Checked Locking - yamasaのネタ帳