ワークの分離#
oneAPI スレッディング・ビルディング・ブロック (oneTBB) では、タスクグループの完了を待機するスレッドが、他の利用可能なタスクを実行することがあります。特に、並列構造が別の並列構造を呼び出す場合、スレッドは内部レベルの完了を待機しつつ、外部レベルの構造からタスクを取得します。
2 つの parallel_for 呼び出しがある次の例では、2 番目の (ネストされた) 並列ループの呼び出しによって、最初の (外側の) ループ反復の実行がブロックされます。
// 最初の並列ループ
oneapi::tbb::parallel_for( 0, N1, []( int i ) {
// 二番目の並列ループ
oneapi::tbb::parallel_for( 0, N2, []( int j ) { /* いくつかのワーク */ } );
} );ブロックされたスレッドは、最初の並列ループに属するタスクを実行できます。その結果、外部ループの 2 つ以上の反復が同じスレッドに同時に割り当てられることがあります。言い換えれば、oneTBB では、並列構造を構成する関数の実行は、単一のスレッド内でも順序付けられません。ほとんどの場合、この振る舞いはスレッドが実行可能な並列処理を制限することはないため、無害であるか有益です。
ただし、状況によっては、順番付けされない実行によりエラーが発生することがあります。例えば、スレッドローカル変数は、入れ子になった並列構造の処理後、予期しない値に変更される場合があります。
oneapi::tbb::enumerable_thread_specific<int> ets;
oneapi::tbb::parallel_for( 0, N1, [&ets]( int i ) {
// スレッド固有の値を設定
ets.local() = i;
oneapi::tbb::parallel_for( 0, N2, []( int j ) { /* Some work */ } );
// 上記の parallel_for の実行中に、スレッドは外側の parallel_for の反復を実行し、
// スレッド固有の値を変更した可能性があります
assert( ets.local()==i ); // アサーションは失敗する可能性があります } );別のシナリオでは、この振る舞いはデッドロックやその他の問題を引き起こす可能性があります。この場合、スレッド内で順序付けして実行することが望まれます。そのため、oneTBB は並列構造の実行を分離する方法を提供します。これにより、タスクは、同時に実行される他のタスクに干渉しなくなります。
これらの方法の 1 つは、内部レベルのループを別の task_arena で実行することです。
oneapi::tbb::enumerable_thread_specific<int> ets;
oneapi::tbb::task_arena nested;
oneapi::tbb::parallel_for( 0, N1, [&]( int i ) {
// スレッド固有の値を設定
ets.local() = i;
nested.execute( []{
// スレッドが外側の parallel_for のタスクを実行しないようにするには、
// 内側の parallel_for を別のアリーナで実行します
oneapi::tbb::parallel_for( 0, N2, []( int j ) { /* いくつかのワーク */ } );
} );
assert( ets.local()==i ); // 有効なアサート
} );ただし、ワークの分離に個別のアリーナを使用することは必然ではなく、オーバーヘッドが生じる可能性があります。これらの欠点を解決するため、oneTBB は、呼び出しスレッドがファンクターのスコープ (分離領域とも呼ばれる) 内でスケジュールされたタスクのみを処理するように制限することで、ユーザー提供のファンクターを分離して実行する this_task_arena::isolate 関数を提供します。
分離領域内でタスク待機呼び出しまたはブロック化された並列構造に入ると、スレッドはその領域内でスポーンされるタスクと他のタスクで生成された子タスクのみを実行します。スレッドは、外部レベルのタスクや他の分離領域に属するタスクの実行が禁止されます。
分離領域は、その領域を呼び出したスレッドだけを制限します。同じタスクアリーナで実行されている他のスレッドは、this_task_arena::isolate への個別の呼び出しで分離されない限り、タスク選択に制限はありません。
次の例は、ネストされた並列構造の呼び出し中にスレッドローカル変数が予期せず変更されないようにするため this_task_arena::isolate を使用する方法を示しています。
#include "oneapi/tbb/task_arena.h"
#include "oneapi/tbb/parallel_for.h"
#include "oneapi/tbb/enumerable_thread_specific.h"
#include <cassert>
int main() {
const int N1 = 1000, N2 = 1000;
oneapi::tbb::enumerable_thread_specific<int> ets;
oneapi::tbb::parallel_for( 0, N1, [&ets]( int i ) {
// スレッド固有の値を設定
ets.local() = i;
// 現在のスレッドが外側の並列ループに関連するタスクを実行しないようにするには、
// 2 番目の並列ループを分離された領域で実行します
oneapi::tbb::this_task_arena::isolate( []{
oneapi::tbb::parallel_for( 0, N2, []( int j ) { /* いくつかのワーク */ } );
} );
assert( ets.local()==i ); // 有効なアサート
} );
return 0;
}