一、private子句
1. private
private 子句可以將變量聲明爲線程私有,聲明稱線程私有變量以後,每個線程都有一個該變量的副本,線程之間不會互相影響,其他線程無法訪問其他線程的副本。原變量在並行部分不起任何作用,也不會受到並行部分內部操作的影響。
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i = 20;
#pragma omp parallel for private(i)
for (i = 0; i < 10; i++)
{
printf("i = %d\n", i);
}
printf("outside i = %d\n", i);
return 0;
}
//運行結果:
i = 0
i = 2
i = 1
i = 4
i = 3
i = 5
i = 6
i = 7
i = 9
i = 8
outside i = 20
2. firstprivate
private子句不能繼承原變量的值,但是有時我們需要線程私有變量繼承原來變量的值,這樣我們就可以使用firstprivate子句來實現。
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel for firstprivate(t)
for (i = 0; i < 5; i++)
{
//次數t被初始化爲20
t += i;
printf("t = %d\n", t);
}
//此時t=20
printf("outside t = %d\n", t);
return 0;
}
3. lastprivate
除了在進入並行部分時需要繼承原變量的值外,有時我們還需要再退出並行部分時將計算結果賦值回原變量,lastprivate子句就可以實現這個需求。
需要注意的是,根據OpenMP規範,在循環迭代中,是最後一次迭代的值賦值給原變量;如果是section結構,那麼是程序語法上的最後一個section語句賦值給原變量。
如果是類(class)變量作爲lastprivate的參數時,我們需要一個缺省構造函數,除非該變量也作爲firstprivate子句的參數;此外還需要一個拷貝賦值操作符。
int main(int argc, char* argv[])
{
int t = 20, i;
// YOUR CODE HERE
#pragma omp parallel for firstprivate(t), lastprivate(t)
// END OF YOUR CODE
for (i = 0; i < 5; i++)
{
t += i;
printf("t = %d\n", t);
}
printf("outside t = %d\n", t);
return 0;
}
===== OUTPUT =====
t = 20
t = 24
t = 23
t = 21
t = 22
outside t = 24
需要注意的是,lastprivate必須要搭配firstprivate一起使用。
4. threadprivate
threadprivate子句可以將一個變量複製一個私有的拷貝給各個線程,即各個線程具有各自私有的全局對象。
int g = 0;
#pragma omp threadprivate(g)
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel
{
g = omp_get_thread_num();
}
#pragma omp parallel
{
printf("thread id: %d g: %d\n", omp_get_thread_num(), g);
}
return 0;
}
這裏threadprivate和private有什麼區別呢?private是對每一次並行任務都複製一份變量,當並行線程數大於等於並行任務的時候,就相當於給每一個線程都複製一個變量,跟threadprivate效果一樣,但是當線程數小於並行任務的時候,線程私有就不能達到private的效果,所以線程私有主要是針對一些面向線程的變量,而不是面向任務的變量。
二、shared子句
上面我們介紹了private子句,相對的,就有shread子句。Share子句可以將一個變量聲明成共享變量,並且在多個線程內共享。需要注意的是,在並行部分進行寫操作時,要求共享變量進行保護,否則不要隨便使用共享變量,儘量將共享變量轉換爲私有變量使用。
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel for shared(t)
for (i = 0; i < 10; i++)
{
if (i % 2 == 0)
t++;
printf("i = %d, t = %d\n", i, t);
}
return 0;
}
===== OUTPUT =====
i = 8, t = 21
i = 1, t = 21
i = 0, t = 22
i = 4, t = 23
i = 6, t = 24
i = 3, t = 24
i = 5, t = 24
i = 7, t = 24
i = 9, t = 24
i = 2, t = 25
這裏使用對t共享,實際上程序變成了串行。
三、OpenMP對private、share的隱式規則
上面是通過private、shared子句顯式地指定數據在線程之間的共享性,當我們不明確指定的時候,規則又是怎麼樣的呢?
OpenMP有一組規則,可以推論出變量的數據共享屬性。
例如,讓我們考慮以下代碼段:
int i = 0;
int n = 10;
int a = 7;
#pragma omp parallel for
for (i = 0; i < n; i++)
{
int b = a + i;
...
}
有四個變量i
,n
, a
和b
。
通常在並行區域之外聲明的變量的數據共享屬性。因此,n
和 a
是共享變量。
但是,循環迭代變量默認情況下是私有的。因此, i
是私有的。
在並行區域內本地聲明的變量是私有的。因此b
是私有的。
我建議在並行區域內聲明循環迭代變量。在這種情況下,很顯然,此變量是私有的。上面的代碼片段如下所示:
int n = 10; // shared
int a = 7; // shared
#pragma omp parallel for
for (int i = 0; i < n; i++) // i private
{
int b = a + i; // b private
...
}
四、默認的變量共享模式
我們可以通過defaut()子句來指定所有變量的共享性,就不用一個一個去打了.
1.default(shared)
default
子句有兩個版本。首先,我們關注default(shared)
選項,然後考慮 default(none)
條款。
這兩個版本特定於OpenMP的C ++程序員。default
Fortran程序員還有其他可能性。
該default(shared)
子句將構造中所有變量的數據共享屬性設置爲共享。在下面的例子中
int a, b, c, n;
...
#pragma omp parallel for default(shared)
for (int i = 0; i < n; i++)
{
// using a, b, c
}
a
,b
,c
和 n
被共享變量。
default(shared)
子句的另一種用法是指定大多數變量的數據共享屬性,然後另外定義私有變量。這種用法如下所示:
int a, b, c, n;
#pragma omp parallel for default(shared) private(a, b)
for (int i = 0; i < n; i++)
{
// a and b are private variables
// c and n are shared variables
}
2.default(none)
該default(none)
子句強制程序員明確指定所有變量的數據共享屬性。
int n = 10;
std::vector<int> vector(n);
int a = 10;
#pragma omp parallel for default(none) shared(n, vector, a)
for (int i = 0; i < n; i++)
{
vector[i] = i * a;
}
3. 兩種模式對循環變量和在並行區域內聲明的變量的影響
1. 循環變量和在並行區域內聲明的變量默認都是私有的
在default(shared)模式下,所有的並行區域外的變量都是shared的模式,對於循環變量和在並行區域內聲明的變量,不受該defautl(shared)模式的影響,還是private的,除非你對它們指定爲shared模式的;
在default(none)情況下,它們同樣不受影響,該模式不會強制你對循環變量和並行區域內變量進行指定,但需要強制你對外部變量進行指定。比如下面,i不會要求強制指定,默認私有,但是不指定a就會報錯。
int i = 20; int a = 1;
#pragma omp parallel for default(none) private(a)
for (i = 0; i < 10; i++)
{
printf("i = %d\t%d\n", i, a);
}
printf("outside i = %d\n", i);
return 0;
五、最佳編程實踐
程序員最好遵循以下兩個準則。
第一條準則是始終使用default(none)
子句編寫並行區域 。這迫使程序員明確考慮所有變量的數據共享屬性。
第二條準則是在可能的情況下在並行區域內聲明私有變量。該準則提高了代碼的可讀性,並使代碼更清晰。
六、參考資料
【1】http://jakascorner.com/blog/2016/06/omp-data-sharing-attributes.html