クリティカルセクション
マルチスレッドでの並行処理を書いているとき、共有リソース(主には共有変数)を同時に書きかえないよう排他アクセスする必要がある。
排他制御を実現するためにコンピューター・サイエンスではセマフォやミューテックスを使う、と習う。 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];
});
}
}