クリティカルセクション

マルチスレッドでの並行処理を書いているとき、共有リソース(主には共有変数)を同時に書きかえないよう排他アクセスする必要がある。

排他制御を実現するためにコンピューター・サイエンスではセマフォやミューテックスを使う、と習う。 Windows ではこれらより軽量なクリティカルセクション(CRITICAL_SECTION)オブジェクトが使える。
プロセスやセッションをまたいだ排他制御が不要──つまりプロセスの中だけ、スレッド間での排他にはクリティカルセクションで足りる。

使いかたは簡単。共有リソースへアクセスする箇所を(初期化済みクリティカルセクションオブジェクトを引数にして) EnterCriticalSection と LeaveCriticalSection でサンドイッチするだけ。

簡単とはいいながら C++ では例外送出もあって、 Enter/Leave の対応関係を確実にするためにはひと工夫が必要。自動変数と RAII を組みあわせたクリティカルセクション管理もできるけれど、 lambda と std::function をつかったアイデアを示しておこう。

struct critical_section : private CRITICAL_SECTION {
  critical_section() { ::InitializeCriticalSection(this); }
  ~critical_section() { ::DeleteCriticalSection(this); }
  void execute(std::function<void(void)> const& block);
};

void critical_section::execute(std::function<void(void)> const& block) {
  ::EnterCriticalSection(this);
  try {
    block();
    ::LeaveCriticalSection(this);
  }
  catch (...) {
    ::LeaveCriticalSection(this);
    throw;
  }
}

このようにつかう:

void foo() {
  auto sum = 0;
  int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, };
#pragma omp parallel for
  for (int i = 0; i < LENGTH_OF(array); ++i) {
    static auto mutex = critical_section();
    mutex.execute([&] {
      sum += array[i];
    });
  }
}

いいなと思ったら応援しよう!