并行计算:OpenMP(二)关于变量的可共享性——private、shared子句

一、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跟threadprivate效果一样,但是当并行任务数量大于线程数的时候,private和threadprivate的效果就不同了。所以线程私有主要是针对一些面向线程的变量,而不是面向任务的变量。

二、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;
    ...
}

    有四个变量in, ab

    通常在并行区域之外声明的变量的数据共享属性。因此,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
    ...
}

四、通过default指定变量共享模式

    我们可以通过defaut()子句来指定所有变量的共享性,就不用一个一个去打了.

1.default(shared)  

 default子句有两个版本。首先,我们关注default(shared)选项,然后考虑 default(none)条款。

    这两个版本特定于OpenMP的C ++程序员。defaultFortran程序员还有其他可能性。

    该default(shared)子句将构造中所有变量的数据共享属性设置为共享。在下面的例子中

int a, b, c, n;
...

#pragma omp parallel for default(shared)
for (int i = 0; i < n; i++)
{
    // using a, b, c
}

 abc和 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

 

  • 17
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值