轉自:http://blog.csdn.net/gengshenghong/article/details/6985431
private/firstprivate/lastprivate/threadprivate,首先要知道的是,它們分為兩大類,一類是private/firstprivate/lastprivate子句,另一類是threadprivate,為指令。(PS:有些地方把threadprivate說成是子句,但是實際來講,它是一個指令。)
可以參考http://blog.csdn.net/gengshenghong/article/details/6970220可以查看哪些指令能接受哪些子句。
(1) private
private子句將一個或多個變量聲明為線程的私有變量。每個線程都有它自己的變量私有副本,其他線程無法訪問。即使在並行區域外有同名的共享變量,共享變量在並行區域內不起任何作用,並且並行區域內不會操作到外面的共享變量。
注意:
1. private variables are undefined on entry and exit of the parallel region.即private變量在進入和退出並行區域是“未定義“的。
2. The value of the original variable (before the parallel region) is undefined after the parallel region!在並行區域之前定義的原來的變量,在並行區域后也是”未定義“的。
3. A private variable within the parallel region has no storage association with the same variable outside of the region. 並行區域內的private變量和並行區域外同名的變量沒有存儲關聯。
說明:private的很容易理解錯誤。下面用例子來說明上面的注意事項,
A. private變量在進入和退出並行區域是”未定義“的。
- int main(int argc, _TCHAR* argv[])
- {
- int A=100;
- #pragma omp parallel for private(A)
- for(int i = 0; i<10;i++)
- {
- printf("%d\n",A);
- }
- return 0;
- }
初學OpenMP很容易認為這段代碼是沒有問題的。其實,這里的A在進入並行區域的時候是未定義的,所以在並行區域直接對其進行讀操作,會導致運行時錯誤。
其實,在VS中編譯這段代碼,就會有編譯警告:
warning C4700: uninitialized local variable 'A' used
很清楚的指向"printf"這句,A是沒有初始化的變量。所以,運行時候會出現運行時崩潰的錯誤。
這段代碼能說明,private在進入並行區域是未定義的,至於退出並行區域就不容易舉例說明了,本身,這里的三個注意事項是交叉理解的,說明的是一個含義,所以,看下面的例子來理解。
B. 在並行區域之前定義的原來的變量,在並行區域后也是”未定義“的。
- int main(int argc, _TCHAR* argv[])
- {
- int B;
- #pragma omp parallel for private(B)
- for(int i = 0; i<10;i++)
- {
- B = 100;
- }
- printf("%d\n",B);
- return 0;
- }
這里的B在並行區域內進行了賦值等操作,但是在退出並行區域后,是未定義的。理解”在並行區域之前定義的原來的變量,在並行區域后也是”未定義“的“這句話的時候,要注意,不是說所有的在並行區域內定義的原來的變量,使用了private子句后,退出並行區域后就一定是未定義的,如果原來的變量,本身已經初始化,那么,退出后,不會處於未定義的狀態,就是下面的第三個注意事項要說明的問題。
C. 並行區域內的private變量和並行區域外同名的變量沒有存儲關聯
- int main(int argc, _TCHAR* argv[])
- {
- int C = 100;
- #pragma omp parallel for private(C)
- for(int i = 0; i<10;i++)
- {
- C = 200;
- printf("%d\n",C);
- }
- printf("%d\n",C);
- return 0;
- }
這里,在退出並行區域后,printf的C的結果是100,和並行區域內對其的操作無關。
總結來說,上面的三點是交叉的,第三點包含了所有的情況。所以,private的關鍵理解是:A private variable within the parallel region has no storage association with the same variable outside of the region. 簡單點理解,可以認為,並行區域內的private變量和並行區域外的變量沒有任何關聯。如果非要說點關聯就是,在使用private的時候,在之前要先定義一下這個變量,但是,到了並行區域后,並行區域的每個線程會產生此變量的副本,而且是沒有初始化的。
下面是綜合上面的例子,參考注釋的解釋:
- int main(int argc, _TCHAR* argv[])
- {
- int A=100,B,C=0;
- #pragma omp parallel for private(A) private(B)
- for(int i = 0; i<10;i++)
- {
- B = A + i; // A is undefined! Runtime error!
- printf("%d\n",i);
- }
- /*--End of OpemMP paralle region. --*/
- C = B; // B is undefined outside of the parallel region!
- printf("A:%d\n", A);
- printf("B:%d\n", B);
- return 0;
- }
(2)firstprivate
Private子句的私有變量不能繼承同名變量的值,firstprivate則用於實現這一功能-繼承並行區域額之外的變量的值,用於在進入並行區域之前進行一次初始化。
Firstprivate(list):All variables in the list areinitialized with the value the original object had before entering the parallelconstruct.
分析下面的例子:
- int main(int argc, _TCHAR* argv[])
- {
- int A;
- #pragma omp parallel for firstprivate(A)
- for(int i = 0; i<10;i++)
- {
- printf("%d: %d\n",i, A); // #1
- }
- printf("%d\n",A); // #2
- return 0;
- }
用VS編譯發現,也會報一個“warning C4700: uninitialized local variable 'A' used”的警告,但是這里其實兩個地方用到了A。實際上,這個警告是針對第二處的,可以看出,VS並沒有給第一處OpenMP並行區域內的A有警告,這是由於使用firstprivate的時候,會對並行區域內的A使用其外的同名共享變量就行初始化,當然,如果嚴格分析,外面的變量其實也是沒有初始化的,理論上也是可以認為應該報警告,但是,具體而言,這是跟VS的實現有關的,另外,在debug下,上面的程序會崩潰,release下,其實是可以輸出值的,總之,上面的輸出是無法預料的。
再看下面的例子,和前面private的例子很類似:
- int main(int argc, _TCHAR* argv[])
- {
- int A = 100;
- #pragma omp parallel for firstprivate(A)
- for(int i = 0; i<10;i++)
- {
- printf("%d: %d\n",i, A); // #1
- }
- printf("%d\n",A); // #2
- return 0;
- }
這里,如果使用private,那么並行區域內是有問題的,因為並行區域內的A是沒有初始化的,導致無法預料的輸出或崩潰。但是,使用了firstprivate后,這樣,進入並行區域的時候,每一個線程的A的副本都會利用並行區域外的同名共享變量A的值進行一次初始化,所以,輸出的A都是100.
繼續探討這里的“進行一次初始化”,為了理解“一次”的含義,看下面的例子:
- #include <omp.h>
- int main(int argc, _TCHAR* argv[])
- {
- int A = 100;
- #pragma omp parallel for firstprivate(A)
- for(int i = 0; i<10;i++)
- {
- printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A); // #1
- A = i;
- }
- printf("%d\n",A); // #2
- return 0;
- }
這里,每次輸出后,改變A的值,需要注意的是,這里的“進行一次初始化”是針對team內的每一個線程進行一次初始化,對於上面的程序,在4核的CPU上運行,並行區域內有四個線程,所以每一個線程都會有A的一個副本,因而,上面的程序輸出結果可能如下:

其實,這個結果是很容易理解的,不可能是每一個for都有一個變量的副本,而是每一個線程,所以這個結果在預料之中。
仍然借助上面這個例子,幫助理解private和firstprivate,從而引出lastprivate,private對於並行區域的每一個線程都有一個副本,並且和並行區域外的變量沒有關聯;firstprivate解決了進入並行區的問題,即在進入並行區域的每個線程的副本變量使用並行區域外的共享變量進行一個初始化的工作,那么下面有一個問題就是,如果希望並行區域的副本變量,在退出並行區的時候,能反過來賦值給並行區域外的共享變量,那么就需要依靠lastprivate了。
(3)lastprivate
如果需要在並行區域內的私有變量經過計算后,在退出並行區域時,需要將其值賦給同名的共享變量,就可以使用lastprivate完成。
Lastprivate(list):The thread that executes the sequentially last iteration or section updates thevalue of the objects in the list.
從上面的firstprivate的最后一個例子可以看出,並行區域對A進行了賦值,但是退出並行區域后,其值仍然為原來的值。
這里首先有一個問題是:退出並行區域后,需要將並行區域內的副本的值賦值為同名的共享變量,那么,並行區域內有多個線程,是哪一個線程的副本用於賦值呢?
是否是最后一個運行完畢的線程?否!OpenMP規范中指出,如果是循環迭代,那么是將最后一次循環迭代中的值賦給對應的共享變量;如果是section構造,那么是最后一個section語句中的值賦給對應的共享變量。注意這里說的最后一個section是指程序語法上的最后一個,而不是實際運行時的最后一個運行完的。
在理解這句話之前,先利用一個簡單的例子來理解一下lastprivate的作用:
- int main(int argc, _TCHAR* argv[])
- {
- int A = 100;
- #pragma omp parallel for lastprivate(A)
- for(int i = 0; i<10;i++)
- {
- A = 10;
- }
- printf("%d\n",A);
- return 0;
- }
這里,很容易知道結果為10,而不是100.這就是lastprivate帶來的效果,退出后會有一個賦值的過程。
理解了lastprivate的基本含義,就可以繼續來理解上面的紅色文字部分的描述了,即到底是哪一個線程的副本用於對並行區域外的變量賦值的問題,下面的例子和前面firstprivate的例子很類似:
- #include <omp.h>
- int main(int argc, _TCHAR* argv[])
- {
- int A = 100;
- #pragma omp parallel for lastprivate(A)
- for(int i = 0; i<10;i++)
- {
- printf("Thread ID: %d, %d\n",omp_get_thread_num(), i); // #1
- A = i;
- }
- printf("%d\n",A); // #2
- return 0;
- }

從結果可以看出,最后並行區域外的共享變量的值並不是最后一個線程退出的值,多次運行發現,並行區域的輸出結果可能發生變化,但是最終的輸出都是9,這就是上面的OpenMP規范說明的問題,退出並行區域的時候,是根據“邏輯上”的最后一個線程用於對共享變量賦值,而不是實際運行的最后一個線程,對於for而言,就是最后一個循環迭代所在線程的副本值,用於對共享變量賦值。
另外,firstprivate和lastprivate分別是利用共享變量對線程副本初始化(進入)以及利用線程副本對共享變量賦值(退出),private是線程副本和共享變量無任何關聯,那么如果希望進入的時候初始化並且退出的時候賦值呢?事實上,可以對同一個變量使用firstprivate和lastprivate的,下面的例子即可看出:
- #include <omp.h>
- int main(int argc, _TCHAR* argv[])
- {
- int A = 100;
- #pragma omp parallel for firstprivate(A) lastprivate(A)
- for(int i = 0; i<10;i++)
- {
- printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A); // #1
- A = i;
- }
- printf("%d\n",A); // #2
- return 0;
- }
說明:不能對一個變量同時使用兩次private,或者同時使用private和firstprivate/lastprivate,只能firstprivate和lastprivate一起使用。
關於lastprivate,還需要說明的一點是,如果是類(class)類型的變量使用在lastprivate參數中,那么使用時有些限制,需要一個可訪問的,明確的缺省構造函數,除非變量也被使用作為firstprivate子句的參數;還需要一個拷貝賦值操作符,並且這個拷貝賦值操作符對於不同對象的操作順序是未指定的,依賴於編譯器的定義。
另外,firstprivate和private可以用於所有的並行構造塊,但是lastprivate只能用於for和section組成的並行塊之中,參考http://blog.csdn.net/gengshenghong/article/details/6970220的對照表。
(4)threadprivate
首先,threadprivate和上面幾個子句的區別在於,threadprivate是指令,不是子句。threadprivate指定全局變量被OpenMP所有的線程各自產生一個私有的拷貝,即各個線程都有自己私有的全局變量。一個很明顯的區別在於,threadprivate並不是針對某一個並行區域,而是整個於整個程序,所以,其拷貝的副本變量也是全局的,即在不同的並行區域之間的同一個線程也是共享的。
threadprivate只能用於全局變量或靜態變量,這是很容易理解的,根據其功能。
根據下面的例子,來進一步理解threadprivate的使用:
- #include <omp.h>
- int A = 100;
- #pragma omp threadprivate(A)
- int main(int argc, _TCHAR* argv[])
- {
- #pragma omp parallel for
- for(int i = 0; i<10;i++)
- {
- A++;
- printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A); // #1
- }
- printf("Global A: %d\n",A); // #2
- #pragma omp parallel for
- for(int i = 0; i<10;i++)
- {
- A++;
- printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A); // #1
- }
- printf("Global A: %d\n",A); // #2
- return 0;
- }

分析結果,發現,第二個並行區域是在第一個並行區域的基礎上繼續遞增的;每一個線程都有自己的全局私有變量。另外,觀察在並行區域外的打印的“Globa A”的值可以看出,這個值總是前面的thread 0的結果,這也是預料之中的,因為退出並行區域后,只有master線程運行。
threadprivate指令也有自己的一些子句,就不在此分析了。另外,如果使用的是C++的類,對於類的構造函數也會有類似於lastprivate的一些限制。
總結:
private/firstprivate/lastprivate都是子句,用於表示並行區域內的變量的數據范圍屬性。其中,private表示並行區域team內的每一個線程都會產生一個並行區域外同名變量的共享變量,且和共享變量沒有任何關聯;firstprivaet在private的基礎上,在進入並行區域時(或說每個線程創建時,或副本變量構造時),會使用並行區域外的共享變量進行一次初始化工作;lastprivate在private的基礎上,在退出並行區域時,會使用並行區域內的副本的變量,對共享變量進行賦值,由於有多個副本,OpenMP規定了如何確定使用哪個副本進行賦值。另外,private不能和firstprivate/lastprivate混用於同一個變量,firstprivate和lastprivate可以對同一變量使用,效果為兩者的結合。
threadprivate是指令,和private的區別在於,private是針對並行區域內的變量的,而threadprivate是針對全局的變量的。
