共用方式為


A. 範例

以下是本文件中定義的建構範例。 指示詞後面的陳述式僅在必要時才是複合語句,而非複合語句則從其前面的指示詞縮進。

A.1 平行執行簡易迴圈

下列範例示範如何使用 parallel for 指示詞平行處理迴圈。 迴圈反覆運算變數預設為私人,所以不需要明確在私人子句中指定它。

#pragma omp parallel for
    for (i=1; i<n; i++)
        b[i] = (a[i] + a[i-1]) / 2.0;

A.2 條件式編譯

下列範例說明如何使用 OpenMP 巨集 _OPENMP 進行條件式編譯。 透過 OpenMP 編譯,_OPENMP 巨集會變成已定義。

# ifdef _OPENMP
    printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif

定義的前置處理器運算子可以在單一指示詞中測試多個巨集。

# if defined(_OPENMP) && defined(VERBOSE)
    printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif

A.3 平行區域

平行指示詞可用於粗粒平行程式。 在下列範例中,平行區域中的每一個執行緒會根據執行緒編號,決定要運作的全域陣列 x 部分:

#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)
{
    iam = omp_get_thread_num();
    np =  omp_get_num_threads();
    ipoints = npoints / np;
    subdomain(x, iam, ipoints);
}

A.4 使用 nowait 子句

如果平行區域內有許多獨立迴圈,您可以使用 nowait 子句來避免 for 指示詞結尾的隱含屏障,如下所示:

#pragma omp parallel
{
    #pragma omp for nowait
        for (i=1; i<n; i++)
             b[i] = (a[i] + a[i-1]) / 2.0;
    #pragma omp for nowait
        for (i=0; i<m; i++)
            y[i] = sqrt(z[i]);
}

A.5 critical 指示詞

下列範例包含數個 critical 指示詞。 此範例描述佇列模型,其中已清除工作的佇列並在處理工作。 若要防範將相同工作排入佇列的許多執行緒,清除佇列作業必須在 critical 區段中。 由於此範例中的兩個佇列是獨立的,因此會受到具有不同名稱 critical 指示詞的保護,xaxisyaxis

#pragma omp parallel shared(x, y) private(x_next, y_next)
{
    #pragma omp critical ( xaxis )
        x_next = dequeue(x);
    work(x_next);
    #pragma omp critical ( yaxis )
        y_next = dequeue(y);
    work(y_next);
}

A.6 使用 lastprivate 子句

正確執行有時取決於迴圈最後一次反覆運算指派給變數的值。 此類程式必須將所有此類變數作為變數列出到 lastprivate 子句,以便變數的值與按順序執行循環時的值相同。

#pragma omp parallel
{
   #pragma omp for lastprivate(i)
      for (i=0; i<n-1; i++)
         a[i] = b[i] + b[i+1];
}
a[i]=b[i];

在上述範例中,平行區域結尾 i 的值會等於 n-1,如同循序案例。

A.7 使用 reduction 子句

下列範例會示範 reduction 子句:

#pragma omp parallel for private(i) shared(x, y, n) \
                         reduction(+: a, b)
    for (i=0; i<n; i++) {
        a = a + x[i];
        b = b + y[i];
    }

A.8 parallel 區段

在下列範例中 (針對 第 2.4.2 節),函式 xaxisyaxis,以及 zaxis 可以同時執行。 第一個 section 指示詞是選用的。 所有 section 指示詞都必須出現在 parallel sections 建構的語彙範圍中。

#pragma omp parallel sections
{
    #pragma omp section
        xaxis();
    #pragma omp section
        yaxis();
    #pragma omp section
        zaxis();
}

A.9 single 指示詞

下列範例示範 single 指示詞。 在此範例中,只有一個執行緒 (通常是第一個遇到 single 指示詞的執行緒) 會列印進度訊息。 使用者不得假設哪個執行緒將執行 single 區段。 所有其他執行緒都會跳過 single 區段,並在 single 建構結尾的屏障處停止。 如果其他執行緒可以在不等待執行 single 區段執行緒的情況下繼續,則可以在 single 指示詞上指定 nowait 子句。

#pragma omp parallel
{
    #pragma omp single
        printf_s("Beginning work1.\n");
    work1();
    #pragma omp single
        printf_s("Finishing work1.\n");
    #pragma omp single nowait
        printf_s("Finished work1 and beginning work2.\n");
    work2();
}

A.10 循序順序

已排序區段有助於按順序對平行完成的工作進行輸出排序。 下列程式會依循序列印出索引:

#pragma omp for ordered schedule(dynamic)
    for (i=lb; i<ub; i+=st)
        work(i);
void work(int k)
{
    #pragma omp ordered
        printf_s(" %d", k);
}

A.11 固定的執行緒數目

某些程式依賴固定且預先指定的執行緒數目,才能正確執行。 由於動態調整執行緒數目的預設設定是實作定義的,因此這類程式可以選擇關閉動態執行緒功能,並明確設定執行緒數目以保持可移植性。 下列範例示範如何使用 omp_set_dynamicomp_set_num_threads 來執行這項操作:

omp_set_dynamic(0);
omp_set_num_threads(16);
#pragma omp parallel shared(x, npoints) private(iam, ipoints)
{
    if (omp_get_num_threads() != 16)
      abort();
    iam = omp_get_thread_num();
    ipoints = npoints/16;
    do_by_16(x, iam, ipoints);
}

在此範例中,只有當程式由 16 個執行緒執行時,才會正確執行。 如果實作無法支援 16 個執行緒,則此範例的行為是實作定義。

不論動態執行緒設定為何,執行平行區域的執行緒數目都會在平行區域中維持不變。 動態執行緒機制會決定在平行區域開始時使用的執行緒數目,並在區域的持續時間內維持不變。

A.12 atomic 指示詞

以下範例透過使用 atomic 指示詞避免了競爭條件 (許多執行緒同時更新 x 的元素):

#pragma omp parallel for shared(x, y, index, n)
    for (i=0; i<n; i++)
    {
        #pragma omp atomic
            x[index[i]] += work1(i);
        y[i] += work2(i);
    }

在此範例中使用 atomic 指示詞的優點是,其可以平行更新 x 的兩個不同元素。 如果改用 critical 指示詞,則對 x 元素的所有更新都將按順序執行 (儘管不能保證任何順序)。

atomic 指示詞只適用於緊接在 C 或 C++ 陳述式之後。 因此,在此範例中,y 的元素不會以不可部分完成的方式更新。

A.13 flush 指示詞並搭配清單

下列範例會使用 flush 指示詞,在執行緒配對之間進行特定對象的點對點同步處理:

int   sync[NUMBER_OF_THREADS];
float work[NUMBER_OF_THREADS];
#pragma omp parallel private(iam,neighbor) shared(work,sync)
{
    iam = omp_get_thread_num();
    sync[iam] = 0;
    #pragma omp barrier

    // Do computation into my portion of work array
    work[iam] = ...;

    //  Announce that I am done with my work
    // The first flush ensures that my work is
    // made visible before sync.
    // The second flush ensures that sync is made visible.
    #pragma omp flush(work)
    sync[iam] = 1;
    #pragma omp flush(sync)

    // Wait for neighbor
    neighbor = (iam>0 ? iam : omp_get_num_threads()) - 1;
    while (sync[neighbor]==0)
    {
        #pragma omp flush(sync)
    }

    // Read neighbor's values of work array
    ... = work[neighbor];
}

A.14 不帶清單的 flush 指示詞

以下範例 (針對第 2.6.5 節) 將受沒有清單的 flush 指示詞影響的共享物件與不受影響的共享物件區分開來:

// omp_flush_without_list.c
#include <omp.h>

int x, *p = &x;

void f1(int *q)
{
    *q = 1;
    #pragma omp flush
    // x, p, and *q are flushed
    //   because they are shared and accessible
    // q is not flushed because it is not shared.
}

void f2(int *q)
{
    #pragma omp barrier
    *q = 2;

    #pragma omp barrier
    // a barrier implies a flush
    // x, p, and *q are flushed
    //   because they are shared and accessible
    // q is not flushed because it is not shared.
}

int g(int n)
{
    int i = 1, j, sum = 0;
    *p = 1;

    #pragma omp parallel reduction(+: sum) num_threads(10)
    {
        f1(&j);
        // i, n and sum were not flushed
        //   because they were not accessible in f1
        // j was flushed because it was accessible
        sum += j;
        f2(&j);
        // i, n, and sum were not flushed
        //   because they were not accessible in f2
        // j was flushed because it was accessible
        sum += i + j + *p + n;
    }
    return sum;
}

int main()
{
}

A.15 使用的執行緒數目

請考慮以下不正確的範例 (針對第 3.1.2 節):

np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
    for (i=0; i<np; i++)
        work(i);

omp_get_num_threads() 調用在代碼循序的區段返回 1,因此在前面的範例中,np 將始終等於 1。 若要確定將為平行區域部署的執行緒數目,調用應位於平行區域內。

下列範例示範如何重寫此程式,而不包括執行緒數目的查詢:

#pragma omp parallel private(i)
{
    i = omp_get_thread_num();
    work(i);
}

A.16 鎖定

在以下範例 (針對第 3.2 節) 中,鎖定函式的引數應具有類型 omp_lock_t,並且無需對其進行排清。 鎖定函式使執行緒在等待進入第一個臨界區段時處於空閒狀態,但在等待進入第二個臨界區段時執行其他工作。 omp_set_lock 函式會阻止,但 omp_test_lock 函式不會,從而允許完成 skip() 中的工作。

// omp_using_locks.c
// compile with: /openmp /c
#include <stdio.h>
#include <omp.h>

void work(int);
void skip(int);

int main() {
   omp_lock_t lck;
   int id;

   omp_init_lock(&lck);
   #pragma omp parallel shared(lck) private(id)
   {
      id = omp_get_thread_num();

      omp_set_lock(&lck);
      printf_s("My thread id is %d.\n", id);

      // only one thread at a time can execute this printf
      omp_unset_lock(&lck);

      while (! omp_test_lock(&lck)) {
         skip(id);   // we do not yet have the lock,
                     // so we must do something else
      }
      work(id);     // we now have the lock
                    // and can do the work
      omp_unset_lock(&lck);
   }
   omp_destroy_lock(&lck);
}

A.17 可巢狀鎖定

以下範例 (針對第 3.2 節) 示範如何使用可巢狀鎖定將更新同步到整個結構及其成員之一。

#include <omp.h>
typedef struct {int a,b; omp_nest_lock_t lck;} pair;

void incr_a(pair *p, int a)
{
    // Called only from incr_pair, no need to lock.
    p->a += a;
}

void incr_b(pair *p, int b)
{
    // Called both from incr_pair and elsewhere,
    // so need a nestable lock.

    omp_set_nest_lock(&p->lck);
    p->b += b;
    omp_unset_nest_lock(&p->lck);
}

void incr_pair(pair *p, int a, int b)
{
    omp_set_nest_lock(&p->lck);
    incr_a(p, a);
    incr_b(p, b);
    omp_unset_nest_lock(&p->lck);
}

void f(pair *p)
{
    extern int work1(), work2(), work3();
    #pragma omp parallel sections
    {
        #pragma omp section
            incr_pair(p, work1(), work2());
        #pragma omp section
            incr_b(p, work3());
    }
}

A.18 巢狀使用指示詞

以下範例的 for 指示詞巢狀是合規的,因為內部和外部 for 指示詞繫結到不同的平行區域:

#pragma omp parallel default(shared)
{
    #pragma omp for
        for (i=0; i<n; i++)
        {
            #pragma omp parallel shared(i, n)
            {
                #pragma omp for
                    for (j=0; j<n; j++)
                        work(i, j);
            }
        }
}

上述範例的下列變化也符合規範:

#pragma omp parallel default(shared)
{
    #pragma omp for
        for (i=0; i<n; i++)
            work1(i, n);
}

void work1(int i, int n)
{
    int j;
    #pragma omp parallel default(shared)
    {
        #pragma omp for
            for (j=0; j<n; j++)
                work2(i, j);
    }
    return;
}

A.19 巢狀使用工作共用指示詞的錯誤範例

本節中的範例說明指示詞巢狀的規則。

下列範例不符合規範,因為內部和外部 for 指示詞是巢狀的,且繫結至相同的 parallel 指示詞:

void wrong1(int n)
{
  #pragma omp parallel default(shared)
  {
      int i, j;
      #pragma omp for
      for (i=0; i<n; i++) {
          #pragma omp for
              for (j=0; j<n; j++)
                 work(i, j);
     }
   }
}

上述範例的下列動態巢狀版本也不符合規範:

void wrong2(int n)
{
  #pragma omp parallel default(shared)
  {
    int i;
    #pragma omp for
      for (i=0; i<n; i++)
        work1(i, n);
  }
}

void work1(int i, int n)
{
  int j;
  #pragma omp for
    for (j=0; j<n; j++)
      work2(i, j);
}

下列範例不符合規範,因為 forsingle 指示詞是巢狀的,且繫結至相同的平行區段:

void wrong3(int n)
{
  #pragma omp parallel default(shared)
  {
    int i;
    #pragma omp for
      for (i=0; i<n; i++) {
        #pragma omp single
          work(i);
      }
  }
}

下列範例不符合規範,因為 for 內的 barrier 指示詞可能會導致鎖死:

void wrong4(int n)
{
  #pragma omp parallel default(shared)
  {
    int i;
    #pragma omp for
      for (i=0; i<n; i++) {
        work1(i);
        #pragma omp barrier
        work2(i);
      }
  }
}

下列範例不符合規範,因為 barrier 會導致鎖死,其原因為一次只能有一個執行緒進入臨界區段:

void wrong5()
{
  #pragma omp parallel
  {
    #pragma omp critical
    {
       work1();
       #pragma omp barrier
       work2();
    }
  }
}

下列範例不符合規範,因為 barrier 會導致鎖死,其原只有一個執行緒能執行single區段:

void wrong6()
{
  #pragma omp parallel
  {
    setup();
    #pragma omp single
    {
      work1();
      #pragma omp barrier
      work2();
    }
    finish();
  }
}

A.20 繫結屏障指示詞

指示詞繫結規則會呼叫 barrier 指示詞,以繫結至最接近的封入 parallel 指示詞。 如需指示詞繫結的詳細資訊,請參閱 第 2.8 節

在下列範例中,從 mainsub2 的呼叫符合規範,因為 barrier (sub3) 會繫結至 sub2 中的平行區域。 從 mainsub1 的呼叫符合規範,因為 barrier 會繫結至 sub2 副程式中的平行區域。 從 mainsub3 的呼叫符合規範,因為 barrier 不會繫結至任何平行區域,因此會被忽略。 此外,barrier 只會同步處理封入平行區域中的執行緒組,而不是 sub1 中建立的所有執行緒。

int main()
{
    sub1(2);
    sub2(2);
    sub3(2);
}

void sub1(int n)
{
    int i;
    #pragma omp parallel private(i) shared(n)
    {
        #pragma omp for
        for (i=0; i<n; i++)
            sub2(i);
    }
}

void sub2(int k)
{
     #pragma omp parallel shared(k)
     sub3(k);
}

void sub3(int n)
{
    work(n);
    #pragma omp barrier
    work(n);
}

A.21 使用 private 子句設定變數範圍

下列範例中 ij 的值在平行區域結束時未定義:

int i, j;
i = 1;
j = 2;
#pragma omp parallel private(i) firstprivate(j)
{
  i = 3;
  j = j + 2;
}
printf_s("%d %d\n", i, j);

如需 private 子句的詳細資訊,請參閱第 2.7.2.1 節

A.22 default(none) 子句

下列範例會將受 default(none) 子句影響的變數與不受其影響的變數區分開:

// openmp_using_clausedefault.c
// compile with: /openmp
#include <stdio.h>
#include <omp.h>

int x, y, z[1000];
#pragma omp threadprivate(x)

void fun(int a) {
   const int c = 1;
   int i = 0;

   #pragma omp parallel default(none) private(a) shared(z)
   {
      int j = omp_get_num_thread();
             //O.K.  - j is declared within parallel region
      a = z[j];       // O.K.  - a is listed in private clause
                      //      - z is listed in shared clause
      x = c;          // O.K.  - x is threadprivate
                      //      - c has const-qualified type
      z[i] = y;       // C3052 error - cannot reference i or y here

      #pragma omp for firstprivate(y)
         for (i=0; i<10 ; i++) {
            z[i] = y;  // O.K. - i is the loop control variable
                       // - y is listed in firstprivate clause
          }
       z[i] = y;   // Error - cannot reference i or y here
   }
}

如需 default 子句的詳細資訊,請參閱第 2.7.2.5 節

A.23 ordered 指示詞範例

有許多已排序的區段,且具有以 ordered 子句指定的 for。 第一個範例不符合規範,因為 API 會指定下列規則:

「具有 for 建構的迴圈反覆運算不得多次執行相同的 ordered 指示詞,並且不得執行多個 ordered 指示詞。」(請參閱第 2.6.6 節)

在此不符合規範的範例中,所有反覆運算都會執行兩個 ordered 區段:

#pragma omp for ordered
for (i=0; i<n; i++)
{
    ...
    #pragma omp ordered
    { ... }
    ...
    #pragma omp ordered
    { ... }
    ...
}

下列符合規範的範例顯示具有多個 ordered 區段的 for

#pragma omp for ordered
for (i=0; i<n; i++)
{
    ...
    if (i <= 10)
    {
        ...
        #pragma omp ordered
        { ... }
    }
    ...
    (i > 10)
    {
        ...
        #pragma omp ordered
        { ... }
    }
    ...
}

A.24 private 子句範例

平行區域的 private 子句只會針對區域的語彙範圍生效,而不是針對區域的動態範圍。 因此,在以下範例中,只要在常式 f 中的 for 迴圈內使用變數 a,即代表 a 的私人副本,而在常式 g 中使用則是指全域 a

int a;

void f(int n)
{
    a = 0;

    #pragma omp parallel for private(a)
    for (int i=1; i<n; i++)
    {
        a = i;
        g(i, n);
        d(a);     // Private copy of "a"
        ...
    }
    ...

void g(int k, int n)
{
    h(k,a); // The global "a", not the private "a" in f
}

A.25 copyprivate 資料屬性子句範例

範例 1:copyprivate 子句可用來將單一執行緒取得的值直接廣播至其他執行緒中私用變數的所有個體。

float x, y;
#pragma omp threadprivate(x, y)

void init( )
{
    float a;
    float b;

    #pragma omp single copyprivate(a,b,x,y)
    {
        get_values(a,b,x,y);
    }

    use_values(a, b, x, y);
}

如果從循序區域呼叫常式 init,則其行為不會受到指示詞的存在影響。 在一個執行緒執行對 get_values 常式的呼叫後,在所有執行緒中由 abxy 指定的私有物件都使用讀取的值進行定義之前,任何執行緒都不會離開建構。

範例 2:與上一個範例相反,假設讀取必須由特定執行緒執行,例如主要執行緒。 在此情況下,copyprivate 子句無法直接執行廣播,但可用來提供暫存共用物件的存取權。

float read_next( )
{
    float * tmp;
    float return_val;

    #pragma omp single copyprivate(tmp)
    {
        tmp = (float *) malloc(sizeof(float));
    }

    #pragma omp master
    {
        get_float( tmp );
    }

    #pragma omp barrier
    return_val = *tmp;
    #pragma omp barrier

    #pragma omp single
    {
       free(tmp);
    }

    return return_val;
}

範例 3:假設平行區域內所需的鎖定物件數目在輸入之前無法輕鬆地判斷。 copyprivate 子句可用來存取該平行區域內配置的共用鎖定物件。

#include <omp.h>

omp_lock_t *new_lock()
{
    omp_lock_t *lock_ptr;

    #pragma omp single copyprivate(lock_ptr)
    {
        lock_ptr = (omp_lock_t *) malloc(sizeof(omp_lock_t));
        omp_init_lock( lock_ptr );
    }

    return lock_ptr;
}

A.26 threadprivate 指示詞

下列範例示範如何使用 threadprivate 指示詞為每個執行緒提供個別的計數器。

範例 1

int counter = 0;
#pragma omp threadprivate(counter)

int sub()
{
    counter++;
    return(counter);
}

範例 2

int sub()
{
    static int counter = 0;
    #pragma omp threadprivate(counter)
    counter++;
    return(counter);
}

A.27 C99 可變長度陣列

下列範例示範如何在 firstprivate 指示詞中使用 C99 可變長度陣列 (VAS)。

注意

Visual C++ 目前不支援可變長度陣列。

void f(int m, int C[m][m])
{
    double v1[m];
    ...
    #pragma omp parallel firstprivate(C, v1)
    ...
}

A.28 num_threads 子句

下列範例會示範 num_threads 子句。 平行區域最多使用 10 個執行緒執行。

#include <omp.h>
main()
{
    omp_set_dynamic(1);
    ...
    #pragma omp parallel num_threads(10)
    {
        ... parallel region ...
    }
}

A.29 critical 建構中的工作共用建構

下列範例示範如何在 critical 建構內使用工作共用建構。 此範例符合規範,因為工作共用建構和 critical 建構不會繫結至相同的平行區域。

void f()
{
  int i = 1;
  #pragma omp parallel sections
  {
    #pragma omp section
    {
      #pragma omp critical (name)
      {
        #pragma omp parallel
        {
          #pragma omp single
          {
            i++;
          }
        }
      }
    }
  }
}

A.30 重新設為私用

下列範例示範變數的重新設為私用。 私用變數可以在巢狀指示詞中再次標記 private。 您不需要在封閉平行區域中共用這些變數。

int i, a;
...
#pragma omp parallel private(a)
{
  ...
  #pragma omp parallel for private(a)
  for (i=0; i<10; i++)
     {
       ...
     }
}

A.31 安全執行緒鎖定函式

下列 C++ 範例示範如何使用 omp_init_lock,初始化平行區域中的鎖定陣列。

// A_13_omp_init_lock.cpp
// compile with: /openmp
#include <omp.h>

omp_lock_t *new_locks() {
   int i;
   omp_lock_t *lock = new omp_lock_t[1000];
   #pragma omp parallel for private(i)
   for (i = 0 ; i < 1000 ; i++)
      omp_init_lock(&lock[i]);

   return lock;
}

int main () {}