Single Construct:
Single construct 在分類上屬於 work-sharing constructs 的一員,因此它的結束點有一個隱性的執行緒同步點存在。在平行區域中,第一個遭遇到 single construct 的執行緒將被指派去執行 construct 內的工作,而其它的執行緒則停在 construct 的結束點直到負責執行 single construct 的執行緒完成工作後才能一起去處理接下來的任務。下圖說明執行緒在 single construct 的工作流程,圖中 TASK 2 位於 single construct 內,因此系統將指派第一個到達這個構造的執行緒去處理 TASK 2:
在 C/C++ 中使用 single construct 的語法如下:
#pragma omp single [clause[[,] clause]...]
structured block
在 Fortran 的語法如下:!$omp single [clause[[,] clause]...]
structured block
!$omp end single [nowait, [copyprivate]]
Master Construct:
Master construct 與 single construct 在操控執行緒的行為上只有兩個地方不同:一是 master construct 保證在構造內的程式只由 master thread (TID = 0) 來執行;二是 master construct 的結束點並沒有隱性的執行緒同步點,所以當主執行緒正在處理 master construct 內的工作時,其它的執行緒會逕自去執行其它的工作。關於 master construct 的動作流程可以使用下圖來說明,注意在 master construct 內的 TASK 2 一定是由主執行緒 (藍色箭條線) 所執行,同一時間其它執行緒會略過 TASK 2 而去處理 TASK 3:
Master construct 在 C/C++ 語法如下:
#pragma omp master
structured block
在 Fortran 的語法如下:!$omp master
structured block
!$omp end master
使用時機:
Single construct 與 master construct 在使用上都必須嵌入在 parallel construct 之中。另外,雖然 single construct 或 master construct 都可以達到在平行區域中將任務委由單一執行緒處理的要求,但因它們在控制執行緒行為上的差異造成我們在選用這兩種 constructs 時會有些限制與考量。一般選用的準則如下:
- 1. 當我們並不在乎構造內的工作該由那一個執行緒去處理,但希望未被指派到的執行緒可以等待該工作完成後再去進行其它任務時宜優先採用 single construct。
- 2. 當只由單一執行緒處理的工作結果不會影響到平行區域內接下來要被執行的任務時宜選用 master construct。
- 1. 在序列執行區將陣列變數 a 的所有元素初值設定為 999。
- 2. 在平行區域內讓單一執行緒對 a 所有的元素重新賦值。
- 3. 使用 loop construct 讓個別執行緒將分配到的陣列元素新值輸出在螢幕上。
//--Program: omp_single_initialized.c
//--Example of OpenMP Single Construct
//--Written by AAZ
#include <stdio.h>
#include <omp.h>
#define NUM 8
int main(int argc, char **argv)
{
int i, tid, a[NUM];
for (i = 0; i < NUM; i++)
a[i] = 999;
#pragma omp parallel private(tid)
{
tid = omp_get_thread_num();
#pragma omp single
{
printf("---TID %d: initialize a[i] ...\n", tid);
for (i = 0; i < NUM; i++) {
a[i] = i;
}
printf("---TID %d: a[i] has been initialized.\n", tid);
} //--End of OMP Single Construct
#pragma omp for
for (i = 0; i < NUM; i++)
printf("TID %d: a[%d] = %d\n", tid, i, a[i]);
} //--End of OMP Parallel Construct
return 0;
}
以下為使用 4 個執行緒執行程式的結果:由上圖可看出賦予 a 陣列所有元素新值的工作是由 TID = 3 的執行緒來負責執行,且在完成這項工作後所有執行緒才一起進入步驟 3 中印出 a 陣列的元素值。整個程式的執行結果與我們的預期結果相符,即陣列變數 a 在進入步驟 3 前已完成所有元素值的更新。
接下來我們來看採用 master construct 進行步驟 2 的情形,完整 C 程式碼與執行結果如下所示:
//--Program: omp_master_initialized.c
//--Example of OpenMP Master Construct
//--Written by AAZ
#include <stdio.h>
#include <omp.h>
#define NUM 8
int main(int argc, char **argv)
{
int i, tid, a[NUM];
for (i = 0; i < NUM; i++)
a[i] = 999;
#pragma omp parallel private(tid)
{
tid = omp_get_thread_num();
#pragma omp master
{
printf("---TID %d: initialize a[i] ...\n", tid);
for (i = 0; i < NUM; i++) {
a[i] = i;
}
printf("---TID %d: a[i] has been initialized.\n", tid);
} //--End of OMP Master Construct
#pragma omp for
for (i = 0; i < NUM; i++)
printf("TID %d: a[%d] = %d\n", tid, i, a[i]);
} //--End of OMP Parallel Construct
return 0;
}
使用 4 個執行緒執行程式的結果:從上圖可以看出程式步驟 2 是由主執行緒 (TID = 0) 所執行,而在主執行緒對 a 陣列所有元素完成重新賦值之前,其它執行緒卻已先進入到程式的第三個步驟將未被更新的元素值印出,造成執行結果與我們預期結果不符的現象。由這個試驗可以了解到使用 master construct 必須留意在主執行緒完成 master construct 內的工作之前是否有其它的執行緒會引用到這些工作的結果,如果有可能會發生這樣的事情則必須在 master construct 之後立即加上 barrier construct 以建立一個顯性的執行緒同步點來強制所有執行緒等待主執行緒完成工作。或者是直接使用 single construct 來取代 master construct,如果我們並不在乎到底是由那一個執行緒來完成需要由單一執行緒來做的工作。附帶說明一點,上圖的執行結果是經過刻意挑選以突顯我們要討論的問題,實際上的執行結果每次都不一樣,也有可能會出現符合我們預期的正確結果,但這是因為我們的範例程式過於簡單的關係,基本上在這樣的狀況下使用 master construct 所產生的結果都是不可信任的。
總結:
- 1. 在平行區域中位於 single construct 與 master construct 內的程式將只由一個執行緒處理。
- 2. Single construct 內的工作由第一個遭遇到它的執行緒負責處理,且在 single construct 結束處有一個隱性的執行緒同步點存在。
- 3. Master construct 內的工作保證是由主執行緒 (TID = 0) 負責處理,而在 master construct 結束處並不存在隱性的執行緒同步點。
- 4. 選用 single construct 或 master construct 時必須考量到位於 construct 內的工作結果是否會在未完成前被其它的執行緒不當地引用,如果有這樣的疑慮就必須採用 single construct,或使用在結束處加上 barrier construct 的 master construct。
(發佈日期:2011/04/26 by AAZ)
沒有留言:
張貼留言