2. 指示詞
指示詞是以 C 和 C++ 標準中所定義的 #pragma
指示詞為基礎。 支援 OpenMP C 和 C++ API 的編譯器將包含命令列選項,可啟動並允許解譯所有 OpenMP 編譯器指示詞。
2.1 指示詞格式
OpenMP 指示詞的語法是由附錄 C 中的文法正式指定,且非正式指定如下:
#pragma omp directive-name [clause[ [,] clause]...] new-line
每個指示詞的開頭都是 #pragma omp
,以減少與其他 (OpenMP 的非 OpenMP 或廠商延伸模組) 具有相同名稱之 pragma 指示詞衝突的可能性。 指示詞的其餘部分會遵循編譯器指示詞 C 和 C++ 標準的慣例。 具體來說,空白字元可以在 #
前後使用,且有時必須使用空白字元來分隔指示詞中的單字。 在 #pragma omp
之後的前置處理權杖可受巨集取代功能影響。
指示詞會區分大小寫。 子句出現在指示詞中的順序並不重要。 指示詞上的子句可能會視需要重複,但受限於每個子句描述中所列的限制。 如果 variable-list 出現在子句中,其必須只指定變數。 每個指示詞只能指定一個指示詞名稱。 例如,不允許下列指示詞:
/* ERROR - multiple directive names not allowed */
#pragma omp parallel barrier
OpenMP 指示詞最多只會套用至一個後續的陳述式,該陳述式必須是結構化區塊。
2.2 條件式編譯
_OPENMP
巨集名稱是由符合 OpenMP 規範的實作所定義為 yyyymm 十進位常數,也就是核准規格的年份和月份。 此巨集不得受 #define
或 #undef
前置處理指示詞所影響。
#ifdef _OPENMP
iam = omp_get_thread_num() + index;
#endif
如果廠商定義 OpenMP 的延伸模組,其可能會指定其他預先定義的巨集。
2.3 平行建構
下列指示詞會定義平行區域,這是由許多執行緒平行執行的程式區域。 此指示詞是啟動平行執行的基本建構。
#pragma omp parallel [clause[ [, ]clause] ...] new-line structured-block
clause 為下列其中一項:
if(
純量運算式)
private(
variable-list)
firstprivate(
variable-list)
default(shared | none)
shared(
variable-list)
copyin(
variable-list)
reduction(
運算子:
variable-list)
num_threads(
整數運算式)
當執行緒進入平行建構時,如果下列其中一個案例成立,就會建立執行緒執行緒組:
if
子句不存在。if
運算式評估為非零值。
此執行緒會成為執行緒組的主執行緒,執行緒數目為 0,且執行緒組中的所有執行緒 (包括主要執行緒) 會以平行方式執行區域。 如果 if
運算式的值為零,則會序列化區域。
若要判斷要求的執行緒數目,將會依序考量下列規則。 符合條件的第一個規則將會套用:
如果
num_threads
子句存在,整數運算式的值就是要求的執行緒數目。如果已呼叫
omp_set_num_threads
程式庫函式,則最近執行之呼叫中的引數值就是要求的執行緒數目。如果已定義環境變數
OMP_NUM_THREADS
,則此環境變數之值就是要求的執行緒數目。如果未使用上述任何方法,則所要求的執行緒數目是由實作定義。
如果存在 num_threads
子句,則只會針對其套用的平行區域取代 omp_set_num_threads
程式庫函式或 OMP_NUM_THREADS
環境變數所要求執行緒數目。 稍後的平行區域不會受其影響。
執行平行區域之執行緒的數目,也取決於是否啟用執行緒數目的動態調整。 如果停用動態調整,則所要求的執行緒數目將會執行平行區域。 如果啟用動態調整,則所要求的執行緒數目是可以執行平行區域的執行緒數目上限。
如果在停用執行緒數目動態調整時遇到平行區域,而且要求平行區域的執行緒數目大於執行階段系統所能提供的數目,則程式的行為是由實作定義。 例如,實作可能會中斷程式的執行,或可能會序列化平行區域。
omp_set_dynamic
程式庫函式和 OMP_DYNAMIC
環境變數可用來啟用和停用執行緒數目的動態調整。
在任何指定時間內,實際裝載執行緒的實體處理器數目,都是由實作定義。 建立執行緒組之後,執行緒組中的執行緒數目會在該平行區域的持續時間內維持不變。 其可以由使用者明確從一個平行區域變更為另一個,或由執行階段系統自動變更。
平行區域動態範圍中包含的陳述式是由每個執行緒執行,而且每個執行緒都可以執行與其他執行緒不同的陳述式路徑。 在平行區域的語彙範圍之外遇到的指示詞,稱為孤立指示詞。
平行區域結尾具有隱含屏障。 只有執行緒組的主要執行緒會在平行區域的結尾繼續執行。
如果執行平行區域之執行緒組中的執行緒遇到另一個平行建構,則會建立新的執行緒組,並成為該新執行緒組的主要執行緒。 巢狀平行區域預設會序列化。 因此,由一個執行緒組成的執行緒組預設會執行巢狀平行區域。 您可以使用執行階段程式庫函式 omp_set_nested
或環境變數 OMP_NESTED
來變更預設行為。 不過,執行巢狀平行區域之執行緒組中的執行緒數目是由實作定義。
parallel
指示詞的限制如下:
指示詞上最多可以出現一個
if
子句。如果出現運算式或
num_threads
運算式,則不會說明其內是否存在任何副作用。在平行區域中執行的
throw
,必須在相同結構化區塊的動態範圍內繼續執行,而且必須由擲回例外狀況的相同執行緒攔截。指示詞上只能出現單一
num_threads
子句。num_threads
運算式是在平行區域的範圍之外進行評估,而且必須評估為正整數值。未指定評估
if
和num_threads
子句的順序。
交互參考
private
、firstprivate
、default
、shared
、copyin
和reduction
子句 (第 2.7.2 節)- OMP_NUM_THREADS 環境變數
- omp_set_dynamic 程式庫函式
- OMP_DYNAMIC 環境變數
- omp_set_nested 函式
- OMP_NESTED 環境變數
- omp_set_num_threads 程式庫函式
2.4 工作共用的建構
工作共用建構會將相關聯陳述式的執行散發給遇到該陳述式的執行緒組成員。 工作共用指示詞不會啟動新的執行緒,而且在進入工作共用建構時不存在隱含屏障。
執行緒組中每個執行緒所遇到的工作共用建構和 barrier
指示詞順序必須相同。
OpenMP 會定義下列工作共用建構,且下列各節會說明這些建構:
2.4.1 for 建構
for
指示詞會識別反覆進行的工作共用建構,其指定相關聯迴圈的反覆運算將會平行執行。 for
迴圈的反覆運算會散發至已存在於執行緒組 (執行迴圈繫結的平行建構) 內的執行緒。 for
建構的語法如下:
#pragma omp for [clause[[,] clause] ... ] new-line for-loop
子句為下列其中一項:
private(
variable-list)
firstprivate(
variable-list)
lastprivate(
variable-list)
reduction(
運算子:
variable-list)
ordered
schedule(
種類 [,
chunk_size])
nowait
for
指示詞會限制對應 for
迴圈的結構。 具體來說,對應的 for
迴圈必須具有規範形:
for (
init-expr ;
var logical-op b ;
incr-expr )
init-expr
下列其中一項:
- var = lb
- integer-type var = lb
incr-expr
下列其中一項:
++
var- var
++
--
var- var
--
- var
+=
incr - var
-=
incr - var
=
var+
incr - var
=
incr+
var - var
=
var-
incr
var
帶正負號的整數變數。 如果此變數在其他情況下共用,則會在 for
持續期間隱含地將其設為私用。 請勿在 for
陳述式的主體內修改此變數。 除非將變數指定為 lastprivate
,否則其迴圈之後的值是不確定的。
logical-op
下列其中一項:
<
<=
>
>=
lb、b 和 incr
迴圈非變異整數運算式。 因為系統不會在這些運算式的評估期間進行同步處理,所以任何評估的副作用都會產生不確定的結果。
規範形允許在進入迴圈時計算迴圈反覆運算的數目。 此計算是採用整數升階後的 var 類型值進行。 具體來說,如果 b -
lb +
incr 的值無法以該類型表示,則結果不確定。 此外,如果 logical-op 為 <
或 <=
,則 incr-expr 必須導致 var 在迴圈的每次反覆運算上增加。 如果 logical-op 為 >
或 >=
,則 incr-expr 必須導致 var 在迴圈的每次反覆運算中變得更小。
schedule
子句會指定如何在執行緒組的執行緒之間分割 for
迴圈的反覆運算。 程式的正確性不得因特定執行緒執行特定反覆運算而遭到損害。 如果指定 chunk_size的值,則其必須是具有正值的迴圈不變異整數運算式。 因為系統不會在此運算式的評估期間進行同步處理,所以任何評估的副作用都會產生不確定的結果。 排程 kind 可以是下列其中一個值:
表 2-1: schedule
子句 kind 值
值 | Description |
---|---|
static | 指定 schedule(static, chunk_size ) 時,反覆運算會分割成大小由 chunk_size 所指定的區塊。 區塊會透過循環配置資源的方式,依執行緒編號的順序靜態指派給執行緒組中的執行緒。 未指定任何 chunk_size 時,反覆運算空間會分成大小大致相等的區塊,同時每個執行緒會獲指派一個區塊。 |
dynamic | 指定 schedule(dynamic, chunk_size ) 時,反覆運算會分成一系列區塊,每個區塊都包含 chunk_size 次反覆運算。 每個區塊都會指派給正在等候指派的執行緒。 執行緒會執行反覆運算區塊,然後等候其下一個指派,直到未獲指派任何區塊為止。 要指派的最後一個區塊可能會有較少的反覆運算次數。 如果未指定 chunk_size,則會預設為 1。 |
guided | 指定 schedule(guided, chunk_size ) 時,反覆運算會以大小遞減之區塊的形式指派給執行緒。 當執行緒完成其指派的反覆運算區塊時,系統會動態地將另一個區塊指派給執行緒,直到沒有任何區塊為止。 在 chunk_size 為 1 的情況下,每個區塊的大小約為未指派的反覆運算數目除以執行緒數目。 這些大小會以近乎指數方式減少至 1。 對於 k 值大於 1 的 chunk_size,大小會以近乎指數方式減少至 k,但最後一個區塊可能少於 k 次反覆運算。 如果未指定 chunk_size,則會預設為 1。 |
執行階段 | 指定 schedule(runtime) 時,排程的相關決策會順延至執行階段。 您可以藉由設定環境變數 OMP_SCHEDULE ,在執行階段選擇排程 kind 和區塊大小。 如果未設定此環境變數,產生的排程會由實作定義。 指定 schedule(runtime) 時,不得指定 chunk_size。 |
如果沒有明確定義的 schedule
子句,則預設 schedule
是由實作定義。
符合 OpenMP 規範的程式,不應將正確執行與否依賴於特定排程。 因為不同編譯器上的相同排程 kind 實作方式可能有所變化,所以程式不應該寄望排程 kind 完全符合上述描述。 上述描述可用來選取適合特定情況的排程。
當 ordered
指示詞系結至 for
建構時,必須存在 ordered
子句。
除非指定 nowait
子句,否則 for
建構結尾會出現隱含屏障。
for
指示詞的限制如下:
for
迴圈必須是結構化區塊,此外,其執行不得由break
陳述式終止。對於執行緒組中的所有執行緒,與
for
指示詞相關聯之for
迴圈的迴圈控制運算式值必須相同。for
迴圈反覆運算變數必須具有帶正負號的整數類型。for
指示詞上只能出現單一schedule
子句。for
指示詞上只能出現單一ordered
子句。for
指示詞上只能出現單一nowait
子句。未指明 chunk_size、lb、b 或 incr 運算式內是否有任何副作用,以及副作用出現的頻率。
執行緒組中所有執行緒的 chunk_size 運算式值必須相同。
交互參考
private
、firstprivate
、lastprivate
和reduction
子句 (第 2.7.2 節)- OMP_SCHEDULE 環境變數
- ordered 建構
- schedule 子句
2.4.2 sections 建構
sections
指示詞會識別非反覆的工作共用建構,該建構會指定要在執行緒組內執行緒之間分割的一組建構。 每個區段都會由執行緒組中的執行緒執行一次。 sections
指示詞的語法如下:
#pragma omp sections [clause[[,] clause] ...] new-line
{
[#pragma omp section new-line]
structured-block
[#pragma omp section new-linestructured-block ]
...
}
子句為下列其中一項:
private(
variable-list)
firstprivate(
variable-list)
lastprivate(
variable-list)
reduction(
運算子:
variable-list)
nowait
雖然第一個區段的 section
指示詞是選用的,但其餘每個區段前面都有 section
指示詞。 section
指示詞必須出現在 sections
指示詞的語彙範圍內。 除非指定 nowait
,否則 sections
建構結尾會出現隱含屏障。
sections
指示詞的限制如下:
section
指示詞不得出現在sections
指示詞的語彙範圍之外。sections
指示詞上只能出現單一nowait
子句。
交互參考
private
、firstprivate
、lastprivate
和reduction
子句 (第 2.7.2 節)
2.4.3 single 建構
single
指示詞會識別建構,該建構指定執行緒組中只有一個執行緒會執行相關聯的結構化區塊 (不一定是主要執行緒)。 single
指示詞的語法如下:
#pragma omp single [clause[[,] clause] ...] new-linestructured-block
子句為下列其中一項:
private(
variable-list)
firstprivate(
variable-list)
copyprivate(
variable-list)
nowait
除非指定 nowait
子句,否則 single
建構後會出現隱含屏障。
single
指示詞的限制如下:
single
指示詞上只能出現單一nowait
子句。copyprivate
子句不能與nowait
子句搭配使用。
交互參考
private
、firstprivate
和copyprivate
子句 (第 2.7.2 節)
2.5 合併的平行工作共用建構
合併的平行工作共用建構是用於指定只有一個工作共用建構之平行區域的快捷方式。 這些指示詞的語意,與明確指定 parallel
指示詞,並在其後接著單一工作共用建構的語意相同。
下列各節會說明合併的平行工作共用建構:
- parallel for 指示詞
- parallel sections 指示詞
2.5.1 parallel for 建構
parallel for
指示詞是只包含單一 for
指示詞之 parallel
區域的快捷方式。 parallel for
指示詞的語法如下:
#pragma omp parallel for [clause[[,] clause] ...] new-linefor-loop
此指示詞允許 parallel
指示詞和 for
指示詞的所有子句 ( nowait
子句除外),並具有相同的意義和限制。 該語意與明確指定 parallel
指示詞緊接 for
指示詞的語意相同。
交互參考
2.5.2 parallel sections 建構
parallel sections
指示詞提供了一個快捷的方式,可用於指定只有單一 sections
指示詞的 parallel
區域。 該語意與明確指定 parallel
指示詞緊接 sections
指示詞的語意相同。 parallel sections
指示詞的語法如下:
#pragma omp parallel sections [clause[[,] clause] ...] new-line
{
[#pragma omp section new-line]
structured-block
[#pragma omp section new-linestructured-block ]
...
}
子句可以是 parallel
和 sections
指示詞所接受的子句之一,但 nowait
子句除外。
交互參考
2.6 主執行緒和同步處理指示詞
下列章節將描述下列項目:
2.6.1 master 建構
master
指示詞會識別建構,該建構會指定執行緒組主執行緒所執行的結構化區塊。 master
指示詞的語法如下:
#pragma omp master new-linestructured-block
執行緒組中的其他執行緒不會執行相關聯的結構化區塊。 在進入或離開主要建構時,不存在隱含屏障。
2.6.2 critical 建構
critical
指示詞會識別一個建構,該建構會限制一次只在單個執行緒上執行相關聯的結構化區塊。 critical
指示詞的語法如下:
#pragma omp critical [(name)] new-linestructured-block
選用的 name 可用來識別關鍵區域。 用來識別關鍵區域的標識碼具有外部連結,且位於之名稱空間與標籤、標記、成員和一般標識碼所使用的名稱空間不同。
執行緒會在關鍵區域的開頭等候,直到沒有其他執行緒正在執行具有相同名稱的關鍵區域 (程式中的任何位置) 為止。 所有未命名 critical
指示詞都會對應至相同的未指定名稱。
2.6.3 barrier 指示詞
barrier
指示詞會同步處理執行緒組中的所有執行緒。 遇到該指示詞時,執行緒組中的每個執行緒都會等候至其他所有執行緒達到這個點為止。 barrier
指示詞的語法如下:
#pragma omp barrier new-line
在執行緒組中的所有執行緒都遇到屏障之後,執行緒組中的每個執行緒都會在屏障指示詞之後平行執行陳述式。 因為 barrier
指示詞在其語法中不含 C 語言陳述式,所以該指示詞存在程式內的位置限制。 如需正式文法的詳細資訊,請參閱附錄 C。下列範例會說明這些限制。
/* ERROR - The barrier directive cannot be the immediate
* substatement of an if statement
*/
if (x!=0)
#pragma omp barrier
...
/* OK - The barrier directive is enclosed in a
* compound statement.
*/
if (x!=0) {
#pragma omp barrier
}
2.6.4 atomic 建構
atomic
指示詞可確保特定記憶體位置會以不可部分完成的方式更新,而不是將其暴露在可能由多個同時寫入執行緒處理的情形下。 atomic
指示詞的語法如下:
#pragma omp atomic new-lineexpression-stmt
運算式陳述式必須具有下列其中一種形式:
- x binop
=
expr - x
++
++
x- x
--
--
x
在上述運算式中:
x 是純量類型的左值運算式。
expr 是具有純量類型的運算式,而且不會參考 x 所指定的物件。
binop 不是多載運算子,而且是
+
、*
、-
、/
、&
、^
、|
、<<
或>>
之一。
雖然是由實作來定義實作是否將所有 atomic
指示詞取代為具有相同唯一 name 的 critical
指示詞,但 atomic
指示詞可實現更好的最佳化。 您通常可以使用硬體指示,以最少的額外負荷執行不可部分完成的更新。
只有 x 所指定物件的載入和儲存動作不可部分完成;但 expr 評估並非不可部分完成。 若要避免競爭條件,應使用 atomic
指示詞來保護所有平行位置的更新,但已知沒有競爭條件的更新除外。
atomic
指示詞的限制如下:
- 在整個程式中,所有對儲存位置 x 的不可部分完成參考,都必須具有相容的類型。
範例
extern float a[], *p = a, b;
/* Protect against races among multiple updates. */
#pragma omp atomic
a[index[i]] += b;
/* Protect against races with updates through a. */
#pragma omp atomic
p[i] -= 1.0f;
extern union {int n; float x;} u;
/* ERROR - References through incompatible types. */
#pragma omp atomic
u.n++;
#pragma omp atomic
u.x -= 1.0f;
2.6.5 flush 指示詞
不論明確還是隱含的 flush
指示詞,都會指定實作所需的「跨執行緒」序列點,以確保執行緒組中的所有執行緒都可以一致檢視記憶體中特定物件 (於下方說明)。 這表示參考這些物件之運算式的先前評估皆已完成,且後續評估尚未開始。 例如,編譯器必須將物件的值從暫存器還原至記憶體,而硬體可能需要將寫入緩衝區排清至記憶體,並從記憶體重新載入物件的值。
flush
指示詞的語法如下:
#pragma omp flush [(variable-list)] new-line
如果所有需要同步處理的物件都可以由變數指定,則可以在選用的 variable-list 指定這些變數。 如果指標存在於 variable-list 中,則指標本身會排清,而不是指標所參考的物件。
沒有 variable-list 的flush
指示詞會同步處理所有共享物件,但有自動儲存期的無法存取物件除外。 (這項作業的額外負荷可能會比具有 variable-list 的 flush
更多。)下列指示詞隱含不具有 variable-list 的 flush
指示詞:
barrier
- 進入和離開
critical
- 進入和離開
ordered
- 進入和離開
parallel
- 離開
for
- 離開
sections
- 離開
single
- 進入和離開
parallel for
- 進入和離開
parallel sections
如果 nowait
子句存在,則不會隱含指示詞。 請注意,下列任何一項都不會隱含 flush
指示詞:
- 進入
for
- 進入或離開
master
- 進入
sections
- 進入
single
可存取具有 volatile 限定型別之物件值的參考,其行為會表現得如同在上一個序列點存在指定該物件的 flush
指示詞一樣。 可修改具有 volatile 限定型別之物件值的參考,其行為會表現得如同在後續序列點存在指定該物件的 flush
指示詞一樣。
因為 flush
指示詞在其語法中不含 C 語言陳述式,所以該指示詞存在程式內的位置限制。 如需正式文法的詳細資訊,請參閱附錄 C。下列範例會說明這些限制。
/* ERROR - The flush directive cannot be the immediate
* substatement of an if statement.
*/
if (x!=0)
#pragma omp flush (x)
...
/* OK - The flush directive is enclosed in a
* compound statement
*/
if (x!=0) {
#pragma omp flush (x)
}
flush
指示詞的限制如下:
flush
指示詞中指定的變數不得具有參考型別。
2.6.6 ordered 建構
在 ordered
指示詞之後的結構化區塊,會依反覆運算在循序迴圈中執行的順序來執行。 ordered
指示詞的語法如下:
#pragma omp ordered new-linestructured-block
ordered
指示詞必須位於 for
或 parallel for
建構的動態範圍內。 ordered
建構繫結的 for
或 parallel for
指示詞必須指定 ordered
子句,如第 2.4.1 節中所述。 在執行具有 ordered
子句的 for
或 parallel for
建構時,ordered
建構會嚴格按照其在迴圈內循序執行的順序來執行。
ordered
指示詞的限制如下:
- 具有
for
構造的迴圈反覆運算不得多次執行相同的 ordered 指示詞,並且不得執行多個ordered
指示詞。
2.7 資料環境
本節將說明指示詞和數個子句,可用於控制平行區域執行期間的資料環境,如下所示:
提供 threadprivate 指示詞,即可將檔案範圍、命名空間範圍或靜態區塊範圍變數設為由執行緒本地執行。
第 2.7.2 節將敘述可在指示詞上指定的子句,其可控制平行或工作共用建構持續期間的變數共用屬性。
2.7.1 threadprivate 指示詞
threadprivate
指示詞會將 variable-list 中指定的具名檔案範圍、命名空間範圍或靜態區塊範圍變數設為執行緒私用。 variable-list 是沒有不完整類型的變數清單 (以逗號分隔)。 threadprivate
指示詞的語法如下:
#pragma omp threadprivate(variable-list) new-line
threadprivate
變數的每個副本都會在程式第一次參考該副本之前的未指定點初始化一次,並以一般方式初始化 (亦即,跟主要副本一樣,會在程式的序列執行中初始化)。 請注意,如果在 threadprivate
變數的明確初始設定式中參考物件,而且物件的值是在首次參考變數副本之前修改,則不會指定行為。
如同任何私用變數,執行緒不得參考另一個執行緒的 threadprivate
物件副本。 在程式的序列區域和主要區域期間,會對主要執行緒的物件副本進行參考。
執行第一個平行區域之後,只有在停用動態執行緒機制,而且所有平行區域的執行緒數量保持不變時,threadprivate
物件中的資料才會保證保存。
threadprivate
指示詞的限制如下:
檔案範圍或命名空間範圍變數的
threadprivate
指示詞,必須出現在任何定義或宣告之外,而且必須在語彙上位於其清單中任何變數的所有參考之前。檔案或命名空間範圍中
threadprivate
指示詞 variable-list 內的每個變數,都必須參考在語彙上位於指示詞之前的檔案或命名空間範圍變數宣告。靜態區塊範圍變數的
threadprivate
指示詞必須出現在變數的範圍中,而不是在巢狀範圍中。 指示詞在語彙上必須位於其清單中所有變數的參考之前。區塊範圍內
threadprivate
指示詞 variable-list 中的每個變數,都必須參考在語彙上位於指示詞之前的相同範圍內變數宣告。 變數宣告必須使用靜態儲存類別規範。如果在一個轉譯單位的
threadprivate
指示詞中指定變數,則必須在宣告變數之每個轉譯單位的threadprivate
指示詞中指定變數。除了
copyin
、copyprivate
、schedule
、num_threads
或if
子句之外,threadprivate
變數不得出現在任何子句中。threadprivate
變數的位址不是位址常數。threadprivate
變數不得具有不完整的類型或參考類型。如果具有非 POD 類別類型的
threadprivate
變數以明確初始設定式宣告,則必須具有可存取且明確的複製建構函式。
下列範例說明修改初始設定式中顯示的變數為何可能會導致未指定的行為,以及如何使用輔助物件和複製建構函式來避免這個問題。
int x = 1;
T a(x);
const T b_aux(x); /* Capture value of x = 1 */
T b(b_aux);
#pragma omp threadprivate(a, b)
void f(int n) {
x++;
#pragma omp parallel for
/* In each thread:
* Object a is constructed from x (with value 1 or 2?)
* Object b is copy-constructed from b_aux
*/
for (int i=0; i<n; i++) {
g(a, b); /* Value of a is unspecified. */
}
}
交互參考
- 動態執行緒
- OMP_DYNAMIC 環境變數
2.7.2 資料共用屬性子句
有數個指示詞可接受讓使用者控制區域期間變數之共用屬性的子句。 共用屬性子句僅適用於出現該子句之指示詞的語彙範圍內變數。 並非所有指示詞都允許下列所有子句。 特定指示詞上有效子句的清單會使用指示詞加以描述。
如果可以在遇到平行或工作共用建構時見到變數,而且未在共用屬性子句或 threadprivate
指示詞中指定變數,則會共用變數。 在平行區域動態範圍內宣告的靜態變數為共用。 堆積配置的記憶體 (例如,在 C、C++ 或 C++ 中的 new
運算子內使用 malloc()
) 為共用。 (不過,這個記憶體的指標可以是私用或共用。)在平行區域之動態範圍內宣告自動儲存期的變數為私用。
大部分子句都接受 variable-list 引數,這是可見變數的清單 (以逗號分隔)。 如果資料共用屬性子句中所參考的變數具有衍生自範本的類型,而且程式中沒有該變數的其他參考,則行為未受定義。
所有出現在指示詞子句內的變數都必須可見。 子句可以視需要重複出現,不過不能在多個子句中指定變數 (但可以在 firstprivate
和 lastprivate
子句中指定變數)。
下列各節將說明資料共享屬性子句:
2.7.2.1 private
private
子句會將 variable-list 中的變數宣告為執行緒組中每個執行緒的私用變數。 private
子句的語法如下:
private(variable-list)
private
子句中指定之變數的行為如下所示。 系統會為建構配置具有自動儲存期的新物件。 新物件的大小和對齊方式,取決於變數的類型。 系統會針對執行緒組中的每個執行緒執行一次此配置,並在必要時為類別物件叫用預設建構函式;若未經過此流程,初始值將無法確定。 在進入建構時具有不確定值的原始物件 (由變數參考),不得在建構的動態範圍內加以修改,而且在從離開建構時具有不確定的值。
在指示詞建構的語彙範圍中,變數會參考執行緒所配置的新私用物件。
private
子句的限制如下:
具有
private
子句中所指定之類別類型的變數,必須具有可存取且明確的預設建構函式。除非
private
子句中指定之變數具備擁有mutable
成員的類別類型,否則其不得具有const
限定型別。在
private
子句內指定的變數,不得具有不完整的類型或參考類型。在
parallel
指示詞reduction
子句中出現的變數,不能在繫結至平行建構之工作共用指示詞的private
子句內指定。
2.7.2.2 firstprivate
firstprivate
子句會提供 private
子句所提供之功能的超集。 firstprivate
子句的語法如下:
firstprivate(variable-list)
variable-list 中指定的變數具有 private
子句語意,如第 2.7.2.1 節所述。 初始化或建構會在執行緒執行建構之前發生 (執行方式和由每個執行緒完成一次的方式類似)。 對於平行建構上的 firstprivate
子句,新私用物件的初始值為原始物件的值,該原始物件會存在於遇到原始物件之執行緒的平行建構之前。 對於工作共用建構上的 firstprivate
子句,執行工作共用建構之每個執行緒的新私用物件初始值,是相同執行緒遇到工作共用建構之前存在的原始物件值。 此外,對於 C++ 物件,每個執行緒的新私用物件都是從原始物件所複製建構的。
firstprivate
子句的限制如下:
在
firstprivate
子句內指定的變數,不得具有不完整的類型或參考類型。具有指定為
firstprivate
之類別類型的變數,必須具有可存取且明確的複製建構函式。在平行區域內私用或在
parallel
指示詞reduction
子句中出現的變數,不能在繫結至平行建構之工作共用指示詞的firstprivate
子句內指定。
2.7.2.3 lastprivate
lastprivate
子句會提供 private
子句所提供之功能的超集。 lastprivate
子句的語法如下:
lastprivate(variable-list)
variable-list 中指定的變數具有 private
子句語意。 當識別工作共用建構的指示詞上出現 lastprivate
子句時,會將來自相關聯迴圈內最後一個反覆運算,或來自語彙上最後一個區段指示詞的每個 lastprivate
變數值,指派給變數的原始物件。 未由 for
或 parallel for
最後一個反覆運算指派值的變數,或是未由 sections
或 parallel sections
指示詞的語彙上最後一個區段指派值的變數,在建構之後具有不確定的值。 未指派的子物件,也會在建構之後具有不確定的值。
lastprivate
子句的限制如下:
private
的所有限制皆適用。具有指定為
lastprivate
之類別類型的變數,必須具有可存取且明確的複製指派運算子。在平行區域內私用或在
parallel
指示詞reduction
子句中出現的變數,不能在繫結至平行建構之工作共用指示詞的lastprivate
子句內指定。
2.7.2.4 shared
此子句會在執行緒組的所有執行緒內共用出現於 variable-list 的變數。 執行緒組內的所有執行緒都會存取 shared
變數的相同儲存區域。
shared
子句的語法如下:
shared(variable-list)
2.7.2.5 default
default
子句可讓使用者影響變數的資料共享屬性。 default
子句的語法如下:
default(shared | none)
除非變數為 threadprivate
或 const
限定型別,否則指定 default(shared)
相當於明確列出 shared
子句中目前可見的每個變數。 如果沒有明確的 default
子句,預設行為會與指定 default(shared)
時相同。
平行建構語彙範圍中變數的每個參考,至少必須符合下列其中一項才能指定 default(none)
:
變數會明確列在包含參考之建構的資料共享屬性子句中。
變數會在平行建構內宣告。
變數為
threadprivate
。變數具有
const
限定型別。變數是
for
迴圈的迴圈控制變數,緊接在for
或parallel for
指示詞後面,而變數參考會出現在迴圈內。
在封入指示詞的 firstprivate
、lastprivate
或 reduction
子句上指定變數,會導致封入內容中的變數隱含參考。 這類隱含參考也受限於以上所列之需求。
您只能在 parallel
指示詞上指定單一 default
子句。
您可以使用 private
、firstprivate
、lastprivate
、reduction
和 shared
子句來覆寫變數的預設資料共享屬性,如下列範例所示:
#pragma omp parallel for default(shared) firstprivate(i)\
private(x) private(r) lastprivate(i)
2.7.2.6 reduction
此子句會使用運算子 op,對出現在 variable-list 中的純量變數執行削減。 reduction
子句的語法如下:
reduction(
op :
variable-list )
實務上通常會對具有下列其中一種形式的陳述式指定削減:
- x
=
x op expr - x binop
=
expr - x
=
expr op x (減法除外) - x
++
++
x- x
--
--
x
其中:
x
清單中指定的其中一個削減變數。
variable-list
純量削減變數的逗號分隔清單。
expr
具有未參考 x 之純量類型的運算式。
op
並非多載運算子,而是 +
、*
、-
、&
、^
、|
、&&
或 ||
之一。
binop
並非多載運算子,而是 +
、*
、-
、&
、^
或 |
之一。
下列是 reduction
子句的範例:
#pragma omp parallel for reduction(+: a, y) reduction(||: am)
for (i=0; i<n; i++) {
a += b[i];
y = sum(y, c[i]);
am = am || b[i] == c[i];
}
如範例所示,運算子可能會隱藏在函式呼叫內。 使用者應該注意,reduction
子句中指定的運算子會與削減作業相符。
雖然 ||
運算子的右運算元在此範例中沒有副作用,可以允許使用,不過在使用上應保持謹慎。 在這種情況下,可能會在平行執行期間發生絕對不會在迴圈循序執行期間發生的副作用。 因為反覆運算的執行順序不定,所以可能會出現這種差異。
運算子可用來判斷編譯器用於進行削減之任何私用變數的初始值,並可用來判斷最終處理運算子。 明確指定運算子,可讓削減陳述式超出建構的語彙範圍。 指示詞上可以指定任意數目的 reduction
子句,但該指示詞的一個 reduction
子句上最多只能會出現一個變數。
系統會如同使用了 private
子句一樣,建立 variable-list 中每個變數的私人複本 (每個執行緒各建立一個)。 系統會根據運算子初始化私人複本 (請參閱下表)。
系統會在指定 reduction
子句的區域結尾更新原始物件,以反映使用指定運算子將其原始值與每個私人複本最終值結合的結果。 削減運算子都是關聯運算子 (減法除外),且編譯器可以隨意重新關聯最終值的計算。 (減法削減的部分結果會相加,以形成最終值。)
當第一個執行緒到達包含子句時,原始物件的值會變得不確定,並在削減計算完成之前保持不確定狀態。 一般而言,計算會在建構結束時完成;不過,如果在同時套用 nowait
的建構上使用 reduction
子句,則原始物件的值會維持在不確定狀態,直到執行屏障同步處理為止,以確保所有執行緒都已完成 reduction
子句。
下表列出有效運算子及其規範初始化值。 實際的初始化值會與削減變數的資料類型一致。
運算子 | 初始化 |
---|---|
+ |
0 |
* |
1 |
- |
0 |
& |
~0 |
| |
0 |
^ |
0 |
&& |
1 |
|| |
0 |
reduction
子句的限制如下:
reduction
子句中的變數類型必須是對削減運算子有效的類型,但一律不允許指標類型和參考型別。reduction
子句中指定的變數不得為const
限定型別。在平行區域內私用或在
parallel
指示詞reduction
子句中出現的變數,不能在繫結至平行建構之工作共用指示詞的reduction
子句內指定。#pragma omp parallel private(y) { /* ERROR - private variable y cannot be specified in a reduction clause */ #pragma omp for reduction(+: y) for (i=0; i<n; i++) y += b[i]; } /* ERROR - variable x cannot be specified in both a shared and a reduction clause */ #pragma omp parallel for shared(x) reduction(+: x)
2.7.2.7 copyin
copyin
子句提供了一個機制,可為執行平行區域之執行緒組內每個執行緒的 threadprivate
變數指派相同的值。 針對 copyin
子句中指定的每個變數,系統會將執行緒組主要執行緒中的變數值,複製到平行區域開頭的執行緒私人複本,就像經過指派一樣。 copyin
子句的語法如下:
copyin(
variable-list
)
copyin
子句的限制如下:
copyin
子句中指定的變數,必須具有可存取且明確的複製指派運算子。copyin
子句中指定的變數,必須是threadprivate
變數。
2.7.2.8 copyprivate
copyprivate
子句提供了一種機制,可以使用私用變數將某個執行緒組成員的值廣播至其他成員。 這是在難以提供共用變數時 (例如,在每個層級需要不同變數的遞迴中),使用共用變數作為值的替代方案。 copyprivate
子句只能出現在 single
指示詞上。
copyprivate
子句的語法如下:
copyprivate(
variable-list
)
copyprivate
子句在其 variable-list 中變數上的效果,會在執行與 single
建構相關聯之結構化區塊之後,且在執行緒組中任何執行緒離開建構結尾屏障之前的這段期間生效。 然後,在執行緒組的所有其他執行緒中,variable-list 內的每個變數都會變成定義變數 (如同經過指派),且帶有執行緒 (執行建構的結構化區塊) 內的對應變數值。
copyprivate
子句的限制如下:
在
copyprivate
子句中指定的變數,不得出現在相同single
指示詞的private
或firstprivate
子句中。如果在平行區域的動態範圍內遇到具有
copyprivate
子句的single
指示詞,則copyprivate
子句中指定的所有變數在封入內容中必須為私用。copyprivate
子句中指定的變數,必須具有可存取且明確的複製指派運算子。
2.8 指示詞繫結
指示詞的動態繫結必須遵循下列規則:
不論動態封入
parallel
上可能存在之任何if
子句的值為何,for
、sections
、single
、master
和barrier
指示詞都會繫結至該指示詞 (如果存在)。 如果目前未執行任何平行區域,僅由主要執行緒組成的執行緒組會負責執行指示詞。ordered
指示詞會繫結至動態封入for
。atomic
指示詞會強制執行所有執行緒中atomic
指示詞的獨佔存取權,而不僅僅是針對目前的執行緒組強制執行。critical
指示詞會強制執行所有執行緒中critical
指示詞的獨佔存取權,而不僅僅是針對目前的執行緒組強制執行。指示詞永遠不能繫結至最接近之動態封入
parallel
以外的任何指示詞。
2.9 指示詞巢狀結構
指示詞的動態巢狀結構必須遵循下列規則:
以邏輯方式動態存在於另一個
parallel
內的parallel
指示詞,會建立新的執行緒組,除非啟用巢狀平行處理原則,否則該執行緒組只會由目前的執行緒組成。不允許繫結至相同
parallel
的for
、sections
和single
指示詞以巢狀方式置於彼此內部。不允許同名的
critical
指示詞以巢狀方式置於彼此內部。 請注意,這項限制不足以防止鎖死。如果
for
、sections
、single
指示詞繫結至與critical
、ordered
和master
區域相同的parallel
,則在這些區域的動態範圍中不允許該指示詞。如果
barrier
指示詞繫結至與for
、ordered
、sections
、single
、master
和critical
區域相同的parallel
,則在這些區域的動態範圍中不允許該指示詞。如果
master
指示詞繫結至與工作共用指示詞相同的parallel
,則在for
、sections
和single
指示詞的動態範圍中不允許master
指示詞。如果
ordered
指示詞繫結至與區域相同的parallel
,則critical
區域的動態範圍中不允許該指示詞。當允許在平行區域內動態執行任何執行指示詞時,也會允許在平行區域外執行該指示詞。 在使用者指定的平行區域外動態執行指示詞時,僅由主要執行緒組成的執行緒組會負責執行指示詞。