タスクとセクションの違いは、コードが実行される時間枠にあります。セクションはsections
構造内に囲まれており、(句が指定されていない限りnowait
) スレッドはすべてのセクションが実行されるまでそれを離れません:
[ sections ]
Thread 0: -------< section 1 >---->*------
Thread 1: -------< section 2 >*------
Thread 2: ------------------------>*------
... *
Thread N-1: ---------------------->*------
ここでN
、スレッドはsections
2 つのセクションを持つ構成に遭遇し、2 番目のセクションは最初のセクションよりも時間がかかります。最初の 2 つのスレッドは、それぞれ 1 つのセクションを実行します。他のN-2
スレッドは、セクション コンストラクトの最後にある暗黙のバリアで待機するだけです (ここでは として表示*
)。
タスクは、いわゆるタスク スケジューリング ポイントで可能な限りキューに入れられ、実行されます。いくつかの条件下では、ランタイムは、存続期間の途中であっても、スレッド間でタスクを移動することが許可される可能性があります。このようなタスクは untied と呼ばれ、untied タスクは 1 つのスレッドで実行を開始し、あるスケジューリング ポイントでランタイムによって別のスレッドに移行される場合があります。
それでも、タスクとセクションは多くの点で似ています。たとえば、次の 2 つのコード フラグメントでは、基本的に同じ結果が得られます。
// sections
...
#pragma omp sections
{
#pragma omp section
foo();
#pragma omp section
bar();
}
...
// tasks
...
#pragma omp single nowait
{
#pragma omp task
foo();
#pragma omp task
bar();
}
#pragma omp taskwait
...
taskwait
非常によく似barrier
ていますが、タスクの場合です。キューに入れられたすべてのタスクが実行されるまで、現在の実行フローが一時停止されます。これはスケジューリング ポイントです。つまり、スレッドがタスクを処理できるようにします。タスクが 1 つのスレッドのみによって作成されるように、このsingle
構造が必要です。コンストラクトがない場合single
、各タスクは作成されたnum_threads
回数を取得しますが、これは望ましくない可能性があります。コンストラクト内のnowait
句は、コンストラクトが実行されるsingle
まで待機しないように他のスレッドに指示しますsingle
(つまり、コンストラクトの最後にある暗黙のバリアを削除しsingle
ます)。taskwait
そのため、彼らはすぐにヒットし、タスクの処理を開始します。
taskwait
わかりやすくするためにここに示す明示的なスケジューリング ポイントです。また、明示的か暗黙的かに関係なく、暗黙的なスケジューリング ポイントがあり、最も顕著なのはバリア同期内です。したがって、上記のコードは次のように単純に記述することもできます。
// tasks
...
#pragma omp single
{
#pragma omp task
foo();
#pragma omp task
bar();
}
...
スレッドが 3 つある場合に起こりうるシナリオの 1 つを次に示します。
+--+-->[ task queue ]--+
| | |
| | +-----------+
| | |
Thread 0: --< single >-| v |-----
Thread 1: -------->|< foo() >|-----
Thread 2: -------->|< bar() >|-----
ここに示されている| ... |
のは、スケジューリング ポイント (taskwait
ディレクティブまたは暗黙のバリア) のアクションです。基本的に、その時点で行っていることをスレッド1
化して中断し、キューからタスクの処理を開始します。2
すべてのタスクが処理されると、スレッドは通常の実行フローを再開します。スレッド1
とは、スレッドが構成を終了する2
前にスケジューリング ポイントに到達する可能性があるため、左側の を整列する必要はありません (これは上の図に示されています)。0
single
|
また、他のスレッドがタスクを要求できるようになる前に、スレッドがタスク1
の処理を終了してfoo()
別のタスクを要求できる場合もあります。したがって、 と の両方foo()
がbar()
同じスレッドによって実行される可能性があります。
+--+-->[ task queue ]--+
| | |
| | +------------+
| | |
Thread 0: --< single >-| v |---
Thread 1: --------->|< foo() >< bar() >|---
Thread 2: --------------------->| |---
スレッド 2 が遅すぎると、選択されたスレッドが 2 番目のタスクを実行する可能性もあります。
+--+-->[ task queue ]--+
| | |
| | +------------+
| | |
Thread 0: --< single >-| v < bar() >|---
Thread 1: --------->|< foo() > |---
Thread 2: ----------------->| |---
場合によっては、コンパイラまたは OpenMP ランタイムがタスク キューを完全にバイパスし、タスクを連続して実行することさえあります。
Thread 0: --< single: foo(); bar() >*---
Thread 1: ------------------------->*---
Thread 2: ------------------------->*---
領域のコード内にタスク スケジューリング ポイントが存在しない場合、OpenMP ランタイムは、適切と判断したときにいつでもタスクを開始する可能性があります。たとえば、parallel
領域の最後にあるバリアに到達するまで、すべてのタスクが延期される可能性があります。