OpenMPとクリティカルセクション
並列プログラミングでは、共有リソースは不可分に処理しないと、正常な結果を得られません。この場合は、クリティカルセクションを使用します。OpenMPを使って、クリティカルセクションを実現したい場合、critical構文(#pragma omp critical)を使用します。
文章だけでは分かりにくいと思いますので、サンプル(C言語使用)を元に解説を続けます。
#include <stdio.h> int main() { int i, x, y, max; max = 10000; /* クリティカルセクションなしで計算 */ x = y = 0; #pragma omp parallel for shared( x, y ) for ( i = 0; i < max; i++ ) { ++x; y += x; } printf( "critical構文を、指定していない場合の値は[x:%d][y:%d]です。\n", x, y ); /* クリティカルセクション内で計算 */ x = y = 0; #pragma omp parallel for shared( x, y ) for ( i = 0; i < max; i++ ) { #pragma omp critical { ++x; y += x; } } printf( "critical構文を、指定した場合の値は[x:%d][y:%d]です。\n\n", x, y ); return 0; }
このサンプルは、共有変数xとyの値を計算しています。変数xとyは、互いに関係が深いと仮定して下さい。サンプルを実行すれば、critical構文を使用しないと、更新の喪失が起こる事が確認できます。
atomic構文を複数使えばいいと思う方もいるでしょうが、変数xとyの関係が深い場合、それぞれの変数がアトミックに更新されても、処理結果は予想外のものになり、正常な処理ではなくなる可能性があります。
何故ならば、並列処理では、同時に複数の処理が実行されますので、他のスレッドが処理した結果が混ざってしまう場合があるからです。処理A内で、処理Bが更新したyの値を使用してしまった場合、処理結果は予想もつかないものになるでしょう。まとまった処理は、クリティカルセクションで一度に指定しましょう。
critical構文は#pragma omp critical( name )のように、クリティカルセクションに名前を付ける事が出来ます。名前を付ける事により、同時に実行できる処理を増やす事が出来ます。
例えば、#pragma omp critical( critical0 )と#pragma omp critical( critical1 )を指定したとします。この場合、名前を付けなかった時とは違い、クリティカルセクション0とクリティカルセクション1内のプログラムが同時に実行されます。一方、名前を付けなかった場合、未指定の名前(unspecified name)と看做され、同時に一つのクリティカルセクション内のプログラムしか処理する事が出来ません。
ただし、複数のクリティカルセクションは、互いに関係がない処理に指定して下さい。関係がある処理に対して、複数のクリティカルセクションを使用すると、正常な処理結果を得られません。
最後に、critical構文を使用するにあたって、一つ注意するべきことを述べます。critical構文を多用しないでください。クリティカルセクションは、共有リソースを単一スレッドのみ処理できるようにする技術なので、並列処理のパフォーマンスを下げます。本当に必要な時だけ使用するようにして下さい。
【参考資料】