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
指示詞的保護,xaxis 和 yaxis。
#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 節),函式 xaxis、yaxis,以及 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_dynamic 和 omp_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);
}
下列範例不符合規範,因為 for
和 single
指示詞是巢狀的,且繫結至相同的平行區段:
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 節。
在下列範例中,從 main 到 sub2 的呼叫符合規範,因為 barrier
(sub3) 會繫結至 sub2 中的平行區域。 從 main 到 sub1 的呼叫符合規範,因為 barrier
會繫結至 sub2 副程式中的平行區域。 從 main 到 sub3 的呼叫符合規範,因為 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 子句設定變數範圍
下列範例中 i
和 j
的值在平行區域結束時未定義:
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 常式的呼叫後,在所有執行緒中由 a、b、x 和 y 指定的私有物件都使用讀取的值進行定義之前,任何執行緒都不會離開建構。
範例 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 () {}