キャンセルとネストした並列処理#
これまでのキャンセルの説明は、ネストされていない並列処理を想定し、task_group_context の詳細を省略することで簡略化しました。ここでは両方について説明します。
oneAPI スレッディング・ビルディング・ブロック (oneTBB) のアルゴリズムは、アルゴリズム・テンプレートに指定したコードを実行する task オブジェクトを作成することで実行されます。デフォルトでは、これらの task オブジェクトは、アルゴリズムによって作成された task_group_context に関連付けられます。ネストされた oneTBB アルゴリズムは、task_group_context オブジェクトのツリーを作成します。task_group_context をキャンセルすると、そのすべての子 task_group_context オブジェクトと、さらにその子孫もすべてキャンセルされます。したがって、アルゴリズム自身と呼び出したすべてのアルゴリズムは、単一のリクエストでキャンセルできます。
例外は上方に伝播します。キャンセルは下方向に伝播します。この反対の相互作用により、例外が発生したときにネストされている計算は適切に停止されます。例えば、次の図のツリーを考えてみましょう。各ノードがアルゴリズムとその task_group_context を表しているとします。
C のアルゴリズムが例外をスローし、どのノードも例外をキャッチしないとします。oneTBB は次のように、例外を上方向に伝播し、関連する下方向のサブツリーをキャンセルします。
C で例外を処理する。
C で例外を取得します。
C 内のタスクをキャンセルします。
C から B に例外をスローします。
B で例外を処理する。
B で例外を取得します。
B 内のタスクをキャンセルし、下方向への伝播によって D 内のタスクもキャンセルします。
B から A に例外をスローします。
A で例外を処理する。
A で例外を取得します。
A 内のタスクをキャンセルし、下方向への伝播によって E、F、G のタスクもキャンセルします。
A から上向きに例外をスローします。
コードがいずれかのレベルで例外をキャッチすると、oneTBB はそれ以上伝播しません。例えば、parallel_for のボディーの外部にエスケープしない例外は、他の反復処理でキャンセルは発生しません。
アルゴリズムへのキャンセルの下位伝播を防ぐには、スタック上に「分離された」 task_group_context を構築してアルゴリズムに明示的に渡します。この例では、簡潔にするため C++11 ラムダ式を使用しています。
#include "oneapi/tbb.h"
bool Data[1000][1000];
int main() {
try {
parallel_for( 0, 1000, 1,
[]( int i ) {
task_group_context root(task_group_context::isolated);
parallel_for( 0, 1000, 1,
[]( int j ) {
Data[i][j] = true;
},
root);
throw "oops";
});
} catch(...){
}
return 0;
}この例では、i に対する外側のループと j に対する内側のループの 2 つの並列ループを実行します。分離された task_group_context root を作成すると、i ループからのキャンセルの下方伝播から内部ループが保護されます。例外が外側のループに伝播すると、保留中の 外側 の反復はすべてキャンセルされますが、開始された外側の反復の内部反復はキャンセルされません。したがって、プログラムが完了すると、反復処理 i が実行されたかどうかに応じて Data の各行が異なる場合がありますが、行内では、要素は均一に false または true になり、混合されることはありません。
task_group_context root(task_group_context::isolated); を削除すると、キャンセルが内部ループに伝播できるようになります。その場合、Data の行には true と false の両方の値が含まれる可能性があります。
