| ナビゲーションリンクをスキップ | |
| 印刷ビューの終了 | |
|   | Oracle Solaris Studio 12.3: OpenMP API ユーザーガイド Oracle Solaris Studio 12.3 Information Library (日本語) | 
タスク化により、OpenMP プログラムを複雑にする要素が加わります。プログラマは、タスクを使用するプログラムがどのように動作するかについて特別な注意を払う必要があります。この節では、プログラミング上で留意すべき問題について説明します。
スレッドがタスクスケジューリングポイントを検出したときは、実装時に現在のタスクが中断され、そのスレッドが他のタスクを処理するようスケジュールされる設定となる場合があります。この動作は、threadprivate 変数の値、またはスレッド番号など他のスレッド固有の情報が、タスクスケジューリングポイントの前後で変更される可能性があることを意味します。
中断されているタスクが結合されている場合は、タスクの実行を再開するスレッドは、中断したときのスレッドと同じになります。このため、スレッド番号はタスクの再開後も変更されません。ただし、threadprivate 変数の値は変更されることがあります。それは、スレッドは他のタスクの処理を行うようスケジューリングされることがあり、中断されたタスクを再開する前に threadprivate 変数が変更される場合があるからです。
中断されているタスクが結合解除場合は、タスクの実行を再開するスレッドは、中断したときのスレッドと異なる場合があります。このため、スレッド番号と threadprivate 変数の値の両方とも、タスクスケジューリングポイントの前後で異なることがあります。
OpenMP では、ロックはスレッドではなく、タスクに所有されていると規定されています。ロックが取得されると、現在のタスクがそれを所有します。タスクの終了時には、同じタスクがそのロックを解放する必要があります。
一方、critical コンストラクトは、スレッドベースの相互排他機構として残されています。
ロックの所有者の変更により、ロックを使用する際にはより慎重な処理が必要となります。次の例 (OpenMP 3.1 仕様の付録 A に記載されています) は OpenMP 2.5 に適合しています。これは、並列領域にあるロック lck を解放するスレッドは、プログラムの順次処理部分でそのロックを取得したのと同じスレッドであるためです。並列領域のマスタースレッドと初期のスレッドが同一です。ところが、このプログラムは OpenMP 3.1 では適合しません。これは、ロック lck を解放するタスク領域が、ロックを取得したタスク領域と異なるためです。
例 5-2 ロックの使用例: OpenMP 3.0 での不適合
#include <stdlib.h>
#include <stdio.h>
#include <omp.h>
int main()
{
  int x;
  omp_lock_t lck;
  omp_init_lock (&lck);
  omp_set_lock (&lck);
  x = 0;
  #pragma omp parallel shared (x)
  {
    #pragma omp master
    {
      x = x + 1;
      omp_unset_lock (&lck);
    }
  }
  omp_destroy_lock (&lck);
}
タスクには、タスクコンストラクトが現れるルーチンのスタック上のデータへの参照が付加されていることがよくあります。タスクの実行は次の暗黙的または明示的バリアーまで保留されることがあるため、指定されたタスクが現れるルーチンのスタックがポップされ、スタックデータが上書きされた後に、そのタスクが実行されることがあります。そのため、タスクに共有されたということでリストアップされたスタックデータが破棄されます。
必要な同期処理を挿入し、タスクが変数を参照したときに、変数が確実にスタック上にあるようにしておくことは、プログラマの責任です。2 つの例を次に示します。
最初の例では、i は task コンストラクトの中で shared に指定されて、タスクは、work() のスタック上に割り当てられている i のコピーにアクセスします。
タスクの実行は保留されることがありますので、タスクは main() の並列領域の最後の暗黙的バリアーで、work() ルーチンの処理の終了後に実行されます。そのため、タスクが i を参照すると、その時にたまたまスタック上にあった値にアクセスしてしまうことになります。
正しい結果を得るためには、プログラマはタスクが完了する前に work() を終了しないようにしておく必要があります。taskwait 指令を task コンストラクトの後に挿入することにより、この処理を追加することができます。あるいは、task コンストラクトで、i に対して shared ではなく、firstprivate を指定することもできます。
例 5-3 スタックデータ: 例 1 - 正しくないバージョン
#include <stdio.h>
#include <omp.h>
void work()
 {
   int i;
   i = 10;
   #pragma omp task shared(i)
   {
     #pragma omp critical
     printf("In Task, i = %d\n",i);
   }
 }
int main(int argc, char** argv)
 {
    omp_set_num_threads(8);
    omp_set_dynamic(0);
    #pragma omp parallel 
    {
      work();
    }
 }例 5-4 スタックデータ: 例 1 - 修正されたバージョン
#include <stdio.h>
#include <omp.h>
void work()
 {
   int i;
   i = 10;
   #pragma omp task shared(i)
   {
     #pragma omp critical
     printf("In Task, i = %d\n",i);
   }
   /* Use TASKWAIT for synchronization. */
   #pragma omp taskwait
 }
int main(int argc, char** argv)
 {
    omp_set_num_threads(8);
    omp_set_dynamic(0);
    #pragma omp parallel 
    {
      work();
    }
 }
2 番目の例では、task 構文中の j が sections 構文中の j を参照しています。このため、タスクは firstprivate 型のコピーである sections 構文中の j にアクセスします。これは、sections 構文のアウトラインルーチンのスタック上のローカル変数です (Oracle Solaris Studio コンパイラを含む一部の実装の場合)。
タスクを sections 領域の最後の暗黙的バリアーで sections コンストラクトのアウトラインルーチン終了後に実行するため、タスクの実行は保留されることがあります。このため、タスクから j が参照されるときには、スタック上の不確定の値がアクセスされてしまいます。
正しい結果を得るためには、プログラマは task 構文のあとに taskwait 指令を挿入して、sections 領域がその暗黙的なバリアーに到達する前にタスクが実行されるようにする必要があります。あるいは、task コンストラクトで、j に対して shared ではなく firstprivate を指定することもできます。
例 5-5 例 2 - 正しくないバージョン
#include <stdio.h>
#include <omp.h>
int main(int argc, char** argv)
 {
    omp_set_num_threads(2);
    omp_set_dynamic(0);
    int j=100;
    #pragma omp parallel shared(j)
    {
       #pragma omp sections firstprivate(j)
       {
          #pragma omp section
          {
             #pragma omp task shared(j)
             {
               #pragma omp critical
               printf("In Task, j = %d\n",j);
             }
          }
       }
    }
    printf("After parallel, j = %d\n",j);
 }例 5-6 例 2 - 修正されたバージョン
#include <stdio.h>
#include <omp.h>
int main(int argc, char** argv)
 {
    omp_set_num_threads(2);
    omp_set_dynamic(0);
    int j=100;
    #pragma omp parallel shared(j)
    {
       #pragma omp sections firstprivate(j)
       {
          #pragma omp section
          {
             #pragma omp task shared(j)
             {
               #pragma omp critical
               printf("In Task, j = %d\n",j);
             }
             /* Use TASKWAIT for synchronization. */
             #pragma omp taskwait
          }
       }
    }
    printf("After parallel, j = %d\n",j);
 }