OpenMP中数据属性相关子句详解(2):shared/default/copyin/copyprivate子句的使用

来源:互联网 发布:无敌9号 知乎 编辑:程序博客网 时间:2024/05/23 11:49



(1) shared

shared子句可以用于声明一个或多个变量为共享变量。所谓的共享变量,是值在一个并行区域的team内的所有线程只拥有变量的一个内存地址,所有线程访问同一地址。所以,对于并行区域内的共享变量,需要考虑数据竞争条件,要防止竞争,需要增加对应的保护,这是程序员需要自行考虑的。

下面的例子是一个求和的并行实现,使用共享变量,由于没有采取保护,会有数据竞争:

#define COUNT10000int main(int argc, _TCHAR* argv[]){int sum = 0;#pragma omp parallel for shared(sum)for(int i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",sum);return 0;}
多次运行,结果可能不一样。

另外,需要注意,循环迭代变量在循环构造区域里是私有的,声明在循环构造区域内的自动变量都是私有的。这一点其实也是比较容易理解的,很难想象,如果循环迭代变量也是共有的,OpenMP该如何去执行,所以也只能是私有的了。即使使用shared来修饰循环迭代变量,也不会改变循环迭代变量在循环构造区域中是私有的这一特点:

#define COUNT10int main(int argc, _TCHAR* argv[]){int sum = 0;int i = 0;#pragma omp parallel for shared(sum, i)for(i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",i);printf("%d\n",sum);return 0;}
这个例子能侧面能说明问题,这里的最后输出i是0,并不是0到COUNT之内的一个可能的值,尽管这里使用shared修饰变量i。注意,这里的规则只是针对循环并行区域,对于其他的并行区域没有这样的要求:

#define COUNT10int main(int argc, _TCHAR* argv[]){int sum = 0;int i = 0;#pragma omp parallel shared(sum) private(i)for(i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",i);printf("%d\n",sum);return 0;}
这里输出的i为0,如果改为shared,那么就是10了。当然,这段代码和上面的求和的代码含义上就是不一样的。

另外,这里顺便一个问题是,在循环并行区域内,循环迭代变量是不可修改的。这也是上面的例子,为何不采用下面的写法:

int i = 0;#pragma omp parallel for shared(sum) shared(i)for(i = 0; i < COUNT;i++){i++;sum = sum + i;}
这里,i++会报错,原因是在循环并行区域内,迭代变量i是可读不可写的。

(2)default

default指定并行区域内变量的属性,C++的OpenMP中default的参数只能为shared或none,对于Fortran,可以去private等参数,具体参考手册。

default(shared):表示并行区域内的共享变量在不指定的情况下都是shared属性

default(none):表示必须显式指定所有共享变量的数据属性,否则会报错,除非变量有明确的属性定义(比如循环并行区域的循环迭代变量只能是私有的)

另外,如果一个并行区域,没有使用default子句,会是什么情况?实际测试,个人认为,没有使用default,那么其默认行为为default(shared)。

#define COUNT10int main(int argc, _TCHAR* argv[]){int sum = 0;int i = 0;#pragma omp parallel forfor(i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",i);printf("%d\n",sum);return 0;}
这里,sum为shared属性,而i的属性不会改变,仍然只能为私有。这里的效果和加上default(shared)是一样的。如果使用default(none),那么编译会报错“没有给sum指定数据共享属性”,不会为变量i报错,因为i是有明确的含义的,只能为私有。

(3)copyin

copyin子句用于将主线程中threadprivate变量的值拷贝到执行并行区域的各个线程的threadprivate变量中,从而使得team内的子线程都拥有和主线程同样的初始值。

参考http://blog.csdn.net/gengshenghong/article/details/6985431中关于threadprivate的例子,修改如下(在第二个并行区域出,增加子句copyin):

#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 copyin(A)    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;  }  


运行此程序,得到的结果和不使用copyin的结果是不一样的。不使用copyin的情况下,进入第二个并行区域的时候,不同线程的私有副本A的初始值是不一样的,这里使用了copyin之后,发现所有的线程的初始值都使用主线程的值初始化,然后继续运算。

为了更好的理解copyin,分析下面的例子:

#include <omp.h>  int A = 100;  #pragma omp threadprivate(A)  int main(int argc, _TCHAR* argv[])  {  #pragma omp parallel{printf("Initial A = %d\n", A);A = omp_get_thread_num();}printf("Global A: %d\n",A);#pragma omp parallel copyin(A)// copyin{printf("Initial A = %d\n", A);A = omp_get_thread_num();}printf("Global A: %d\n",A);#pragma omp parallel// Will not copy, to check the result.{printf("Initial A = %d\n", A);A = omp_get_thread_num();}printf("Global A: %d\n",A);return 0;  

简单理解,在使用了copyin后,所有的线程的threadprivate类型的副本变量都会与主线程的副本变量进行一次“同步”。

另外,copyin中的参数必须被声明成threadprivate的,对于类类型的变量,必须带有明确的拷贝赋值操作符。而且,对于第一个并行区域,是默认含有copyin的功能的(比如上面的例子的前面的四个A的输出都是100)。copyin的一个可能需要用到的情况是,比如程序中有多个并行区域,每个线程希望保存一个私有的全局变量,但是其中某一个并行区域执行前,希望与主线程的值相同,就可以利用copyin进行赋值。

(4)copyprivate

copyprivate子句用于将线程私有副本变量的值从一个线程广播到执行同一并行区域的其他线程的同一变量。

说明:copyprivate只能用于single指令的子句中,在一个single块的结尾处完成广播操作。copyprivate只能用于private/firstprivate或threadprivate修饰的变量。

根据下面的程序,可以理解copyprivate的使用:

#include <omp.h>  int A = 100;  #pragma omp threadprivate(A)  int main(int argc, _TCHAR* argv[])  {  int B = 100;int C = 1000;#pragma omp parallel firstprivate(B) copyin(A)// copyin(A) can be ignored!{#pragma omp single copyprivate(A) copyprivate(B)// copyprivate(C)// C is shared, cannot use copyprivate!{A = 10;B = 20;}printf("Initial A = %d\n", A);// 10 for all threadsprintf("Initial B = %d\n", B);// 20 for all threads}printf("Global A: %d\n",A);// 10printf("Global A: %d\n",B);// 100. B is still 100! Will not be affected here!return 0;  }

(1) shared

shared子句可以用于声明一个或多个变量为共享变量。所谓的共享变量,是值在一个并行区域的team内的所有线程只拥有变量的一个内存地址,所有线程访问同一地址。所以,对于并行区域内的共享变量,需要考虑数据竞争条件,要防止竞争,需要增加对应的保护,这是程序员需要自行考虑的。

下面的例子是一个求和的并行实现,使用共享变量,由于没有采取保护,会有数据竞争:

#define COUNT10000int main(int argc, _TCHAR* argv[]){int sum = 0;#pragma omp parallel for shared(sum)for(int i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",sum);return 0;}
多次运行,结果可能不一样。

另外,需要注意,循环迭代变量在循环构造区域里是私有的,声明在循环构造区域内的自动变量都是私有的。这一点其实也是比较容易理解的,很难想象,如果循环迭代变量也是共有的,OpenMP该如何去执行,所以也只能是私有的了。即使使用shared来修饰循环迭代变量,也不会改变循环迭代变量在循环构造区域中是私有的这一特点:

#define COUNT10int main(int argc, _TCHAR* argv[]){int sum = 0;int i = 0;#pragma omp parallel for shared(sum, i)for(i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",i);printf("%d\n",sum);return 0;}
这个例子能侧面能说明问题,这里的最后输出i是0,并不是0到COUNT之内的一个可能的值,尽管这里使用shared修饰变量i。注意,这里的规则只是针对循环并行区域,对于其他的并行区域没有这样的要求:

#define COUNT10int main(int argc, _TCHAR* argv[]){int sum = 0;int i = 0;#pragma omp parallel shared(sum) private(i)for(i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",i);printf("%d\n",sum);return 0;}
这里输出的i为0,如果改为shared,那么就是10了。当然,这段代码和上面的求和的代码含义上就是不一样的。

另外,这里顺便一个问题是,在循环并行区域内,循环迭代变量是不可修改的。这也是上面的例子,为何不采用下面的写法:

int i = 0;#pragma omp parallel for shared(sum) shared(i)for(i = 0; i < COUNT;i++){i++;sum = sum + i;}
这里,i++会报错,原因是在循环并行区域内,迭代变量i是可读不可写的。

(2)default

default指定并行区域内变量的属性,C++的OpenMP中default的参数只能为shared或none,对于Fortran,可以去private等参数,具体参考手册。

default(shared):表示并行区域内的共享变量在不指定的情况下都是shared属性

default(none):表示必须显式指定所有共享变量的数据属性,否则会报错,除非变量有明确的属性定义(比如循环并行区域的循环迭代变量只能是私有的)

另外,如果一个并行区域,没有使用default子句,会是什么情况?实际测试,个人认为,没有使用default,那么其默认行为为default(shared)。

#define COUNT10int main(int argc, _TCHAR* argv[]){int sum = 0;int i = 0;#pragma omp parallel forfor(i = 0; i < COUNT;i++){sum = sum + i;}printf("%d\n",i);printf("%d\n",sum);return 0;}
这里,sum为shared属性,而i的属性不会改变,仍然只能为私有。这里的效果和加上default(shared)是一样的。如果使用default(none),那么编译会报错“没有给sum指定数据共享属性”,不会为变量i报错,因为i是有明确的含义的,只能为私有。

(3)copyin

copyin子句用于将主线程中threadprivate变量的值拷贝到执行并行区域的各个线程的threadprivate变量中,从而使得team内的子线程都拥有和主线程同样的初始值。

参考http://blog.csdn.net/gengshenghong/article/details/6985431中关于threadprivate的例子,修改如下(在第二个并行区域出,增加子句copyin):

#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 copyin(A)    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;  }  


运行此程序,得到的结果和不使用copyin的结果是不一样的。不使用copyin的情况下,进入第二个并行区域的时候,不同线程的私有副本A的初始值是不一样的,这里使用了copyin之后,发现所有的线程的初始值都使用主线程的值初始化,然后继续运算。

为了更好的理解copyin,分析下面的例子:

#include <omp.h>  int A = 100;  #pragma omp threadprivate(A)  int main(int argc, _TCHAR* argv[])  {  #pragma omp parallel{printf("Initial A = %d\n", A);A = omp_get_thread_num();}printf("Global A: %d\n",A);#pragma omp parallel copyin(A)// copyin{printf("Initial A = %d\n", A);A = omp_get_thread_num();}printf("Global A: %d\n",A);#pragma omp parallel// Will not copy, to check the result.{printf("Initial A = %d\n", A);A = omp_get_thread_num();}printf("Global A: %d\n",A);return 0;  

简单理解,在使用了copyin后,所有的线程的threadprivate类型的副本变量都会与主线程的副本变量进行一次“同步”。

另外,copyin中的参数必须被声明成threadprivate的,对于类类型的变量,必须带有明确的拷贝赋值操作符。而且,对于第一个并行区域,是默认含有copyin的功能的(比如上面的例子的前面的四个A的输出都是100)。copyin的一个可能需要用到的情况是,比如程序中有多个并行区域,每个线程希望保存一个私有的全局变量,但是其中某一个并行区域执行前,希望与主线程的值相同,就可以利用copyin进行赋值。

(4)copyprivate

copyprivate子句用于将线程私有副本变量的值从一个线程广播到执行同一并行区域的其他线程的同一变量。

说明:copyprivate只能用于single指令的子句中,在一个single块的结尾处完成广播操作。copyprivate只能用于private/firstprivate或threadprivate修饰的变量。

根据下面的程序,可以理解copyprivate的使用:

#include <omp.h>  int A = 100;  #pragma omp threadprivate(A)  int main(int argc, _TCHAR* argv[])  {  int B = 100;int C = 1000;#pragma omp parallel firstprivate(B) copyin(A)// copyin(A) can be ignored!{#pragma omp single copyprivate(A) copyprivate(B)// copyprivate(C)// C is shared, cannot use copyprivate!{A = 10;B = 20;}printf("Initial A = %d\n", A);// 10 for all threadsprintf("Initial B = %d\n", B);// 20 for all threads}printf("Global A: %d\n",A);// 10printf("Global A: %d\n",B);// 100. B is still 100! Will not be affected here!return 0;  }