转自: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变量在进入和退出并行区域是”未定义“的。

  1. int main(int argc, _TCHAR* argv[])
  2. {
  3. int A=100;
  4. #pragma omp parallel for private(A)
  5. for(int i = 0; i<10;i++)
  6. {
  7. printf("%d\n",A);
  8. }
  9. return 0;
  10. }

初学OpenMP很容易认为这段代码是没有问题的。其实,这里的A在进入并行区域的时候是未定义的,所以在并行区域直接对其进行读操作,会导致运行时错误。

其实,在VS中编译这段代码,就会有编译警告:

warning C4700: uninitialized local variable 'A' used

很清楚的指向"printf"这句,A是没有初始化的变量。所以,运行时候会出现运行时崩溃的错误。

这段代码能说明,private在进入并行区域是未定义的,至于退出并行区域就不容易举例说明了,本身,这里的三个注意事项是交叉理解的,说明的是一个含义,所以,看下面的例子来理解。

B. 在并行区域之前定义的原来的变量,在并行区域后也是”未定义“的。

  1. int main(int argc, _TCHAR* argv[])
  2. {
  3. int B;
  4. #pragma omp parallel for private(B)
  5. for(int i = 0; i<10;i++)
  6. {
  7. B = 100;
  8. }
  9. printf("%d\n",B);
  10. return 0;
  11. }

这里的B在并行区域内进行了赋值等操作,但是在退出并行区域后,是未定义的。理解”在并行区域之前定义的原来的变量,在并行区域后也是”未定义“的“这句话的时候,要注意,不是说所有的在并行区域内定义的原来的变量,使用了private子句后,退出并行区域后就一定是未定义的,如果原来的变量,本身已经初始化,那么,退出后,不会处于未定义的状态,就是下面的第三个注意事项要说明的问题。

C. 并行区域内的private变量和并行区域外同名的变量没有存储关联

  1. int main(int argc, _TCHAR* argv[])
  2. {
  3. int C = 100;
  4. #pragma omp parallel for private(C)
  5. for(int i = 0; i<10;i++)
  6. {
  7. C = 200;
  8. printf("%d\n",C);
  9. }
  10. printf("%d\n",C);
  11. return 0;
  12. }

这里,在退出并行区域后,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的时候,在之前要先定义一下这个变量,但是,到了并行区域后,并行区域的每个线程会产生此变量的副本,而且是没有初始化的。

下面是综合上面的例子,参考注释的解释:

  1. int main(int argc, _TCHAR* argv[])
  2. {
  3. int A=100,B,C=0;
  4. #pragma omp parallel for private(A) private(B)
  5. for(int i = 0; i<10;i++)
  6. {
  7. B = A + i;      // A is undefined! Runtime error!
  8. printf("%d\n",i);
  9. }
  10. /*--End of OpemMP paralle region. --*/
  11. C = B;          // B is undefined outside of the parallel region!
  12. printf("A:%d\n", A);
  13. printf("B:%d\n", B);
  14. return 0;
  15. }

(2)firstprivate

Private子句的私有变量不能继承同名变量的值,firstprivate则用于实现这一功能-继承并行区域额之外的变量的值,用于在进入并行区域之前进行一次初始化

Firstprivate(list):All variables in the list areinitialized with the value the original object had before entering the parallelconstruct.

分析下面的例子:

  1. int main(int argc, _TCHAR* argv[])
  2. {
  3. int A;
  4. #pragma omp parallel for firstprivate(A)
  5. for(int i = 0; i<10;i++)
  6. {
  7. printf("%d: %d\n",i, A);    // #1
  8. }
  9. printf("%d\n",A);   // #2
  10. return 0;
  11. }

用VS编译发现,也会报一个“warning C4700: uninitialized local variable 'A' used”的警告,但是这里其实两个地方用到了A。实际上,这个警告是针对第二处的,可以看出,VS并没有给第一处OpenMP并行区域内的A有警告,这是由于使用firstprivate的时候,会对并行区域内的A使用其外的同名共享变量就行初始化,当然,如果严格分析,外面的变量其实也是没有初始化的,理论上也是可以认为应该报警告,但是,具体而言,这是跟VS的实现有关的,另外,在debug下,上面的程序会崩溃,release下,其实是可以输出值的,总之,上面的输出是无法预料的。

再看下面的例子,和前面private的例子很类似:

  1. int main(int argc, _TCHAR* argv[])
  2. {
  3. int A = 100;
  4. #pragma omp parallel for firstprivate(A)
  5. for(int i = 0; i<10;i++)
  6. {
  7. printf("%d: %d\n",i, A);    // #1
  8. }
  9. printf("%d\n",A);   // #2
  10. return 0;
  11. }

这里,如果使用private,那么并行区域内是有问题的,因为并行区域内的A是没有初始化的,导致无法预料的输出或崩溃。但是,使用了firstprivate后,这样,进入并行区域的时候,每一个线程的A的副本都会利用并行区域外的同名共享变量A的值进行一次初始化,所以,输出的A都是100.

继续探讨这里的“进行一次初始化”,为了理解“一次”的含义,看下面的例子:

  1. #include <omp.h>
  2. int main(int argc, _TCHAR* argv[])
  3. {
  4. int A = 100;
  5. #pragma omp parallel for firstprivate(A)
  6. for(int i = 0; i<10;i++)
  7. {
  8. printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A);   // #1
  9. A = i;
  10. }
  11. printf("%d\n",A);   // #2
  12. return 0;
  13. }

这里,每次输出后,改变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的作用:

  1. int main(int argc, _TCHAR* argv[])
  2. {
  3. int A = 100;
  4. #pragma omp parallel for lastprivate(A)
  5. for(int i = 0; i<10;i++)
  6. {
  7. A = 10;
  8. }
  9. printf("%d\n",A);
  10. return 0;
  11. }

这里,很容易知道结果为10,而不是100.这就是lastprivate带来的效果,退出后会有一个赋值的过程。

理解了lastprivate的基本含义,就可以继续来理解上面的红色文字部分的描述了,即到底是哪一个线程的副本用于对并行区域外的变量赋值的问题,下面的例子和前面firstprivate的例子很类似:

  1. #include <omp.h>
  2. int main(int argc, _TCHAR* argv[])
  3. {
  4. int A = 100;
  5. #pragma omp parallel for lastprivate(A)
  6. for(int i = 0; i<10;i++)
  7. {
  8. printf("Thread ID: %d, %d\n",omp_get_thread_num(), i);  // #1
  9. A = i;
  10. }
  11. printf("%d\n",A);   // #2
  12. return 0;
  13. }

从结果可以看出,最后并行区域外的共享变量的值并不是最后一个线程退出的值,多次运行发现,并行区域的输出结果可能发生变化,但是最终的输出都是9,这就是上面的OpenMP规范说明的问题,退出并行区域的时候,是根据“逻辑上”的最后一个线程用于对共享变量赋值,而不是实际运行的最后一个线程,对于for而言,就是最后一个循环迭代所在线程的副本值,用于对共享变量赋值。

另外,firstprivate和lastprivate分别是利用共享变量对线程副本初始化(进入)以及利用线程副本对共享变量赋值(退出),private是线程副本和共享变量无任何关联,那么如果希望进入的时候初始化并且退出的时候赋值呢?事实上,可以对同一个变量使用firstprivate和lastprivate的,下面的例子即可看出:

  1. #include <omp.h>
  2. int main(int argc, _TCHAR* argv[])
  3. {
  4. int A = 100;
  5. #pragma omp parallel for firstprivate(A) lastprivate(A)
  6. for(int i = 0; i<10;i++)
  7. {
  8. printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A);   // #1
  9. A = i;
  10. }
  11. printf("%d\n",A);   // #2
  12. return 0;
  13. }

说明:不能对一个变量同时使用两次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的使用:

  1. #include <omp.h>
  2. int A = 100;
  3. #pragma omp threadprivate(A)
  4. int main(int argc, _TCHAR* argv[])
  5. {
  6. #pragma omp parallel for
  7. for(int i = 0; i<10;i++)
  8. {
  9. A++;
  10. printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A);   // #1
  11. }
  12. printf("Global A: %d\n",A); // #2
  13. #pragma omp parallel for
  14. for(int i = 0; i<10;i++)
  15. {
  16. A++;
  17. printf("Thread ID: %d, %d: %d\n",omp_get_thread_num(), i, A);   // #1
  18. }
  19. printf("Global A: %d\n",A); // #2
  20. return 0;
  21. }

分析结果,发现,第二个并行区域是在第一个并行区域的基础上继续递增的;每一个线程都有自己的全局私有变量。另外,观察在并行区域外的打印的“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是针对全局的变量的。

[转]OpenMP中的private/firstprivate/lastprivate/threadprivate之间的比较的更多相关文章

  1. Java中public,private,protected,和默认的区别

    Java中public,private,protected,和默认的区别 1.private修饰词,表示成员是私有的,只有自身可以访问: 2.protected,表示受保护权限,体现在继承,即子类可以 ...

  2. 并行计算之OpenMP中的任务调度

    本文参考<OpenMP中的任务调度>博文,主要讲的是OpenMP中的schedule子句用法. 一.应用需求 在OpenMP并行计算中,任务调度主要用于并行的for循环.当for循环中每次 ...

  3. php class中public,private,protected的区别,以及实例

    一,public,private,protected的区别 public:权限是最大的,可以内部调用,实例调用等. protected: 受保护类型,用于本类和继承类调用. private: 私有类型 ...

  4. OpenMP 中的线程任务调度

    OpenMP中任务调度主要针对并行的for循环,当循环中每次迭代的计算量不相等时,如果简单地给各个线程分配相同次数的迭代,则可能会造成各个线程计算负载的不平衡,影响程序的整体性能. 如下面的代码中,如 ...

  5. OpenMP中的同步和互斥

    在多线程编程中必须考虑到不同的线程对同一个变量进行读写访问引起的数据竞争问题.如果线程间没有互斥机制,则不同线程对同一变量的访问顺序是不确定的,有可能导致错误的执行结果. OpenMP中有两种不同类型 ...

  6. [转]OpenMP中几个容易混淆的函数(线程数量/线程ID/线程最大数)以及并行区域线程数量的确定

    说明:这部分内容比较基础,主要是分析几个容易混淆的OpenMP函数,加以理解. (1)并行区域数量的确定: 在这里,先回顾一下OpenMP的parallel并行区域线程数量的确定,对于一个并行区域,有 ...

  7. [原创]java WEB学习笔记81:Hibernate学习之路--- 对象关系映射文件(.hbm.xml):hibernate-mapping 节点,class节点,id节点(主键生成策略),property节点,在hibernate 中 java类型 与sql类型之间的对应关系,Java 时间和日期类型的映射,Java 大对象类型 的 映射 (了解),映射组成关系

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  8. Spring中Model、ModelMap及ModelAndView之间的区别

    Spring中Model.ModelMap及ModelAndView之间的区别   1. Model(org.springframework.ui.Model)Model是一个接口,包含addAttr ...

  9. C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现

    C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现 来源 http://blog.csdn.net/Virtual_Func/article/details/4975 ...

随机推荐

  1. Eclipse代码布局怎么使用退格和缩进快捷键?

    Eclipse代码布局怎么使用退格和缩进快捷键? 好的程序,不仅要运行快速准确,而且还要易于理解.研究表明,清晰的代码布局可以提高程序猿的理解能力.何为代码布局?其实就是代码的缩进.留白等.为了保证清 ...

  2. (转)一个基于vue2的天气js应用

    基于vue.js 2.0的百度天气应用 vue-weather 基于vue.js 2.0的百度天气应用. 说明 初学vue,在看完一个简单的视频教程和走两遍完官方文档之后仍然感觉云里雾里,知其然不知其 ...

  3. 转: RSA原理 阮一峰的博客

    转:http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html 讲的非常细致,易懂.

  4. CyclicBarrier使用方法

    CyclicBarrier是一个同步辅助类,它同意一组线程互相等待.直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待, ...

  5. 对Class.getResourceAsStream和ClassLoader.getResourceAsStream方法所使用的资源路径的解释

    这是个非常基础的问题了,这里提供一些演示样例,帮助高速理解和记忆这个问题. 在该方法的文档:http://docs.oracle.com/javase/7/docs/api/java/lang/Cla ...

  6. Spark1.0.0 history server 配置

    在执行Spark应用程序的时候,driver会提供一个webUI给出应用程序的执行信息.可是该webUI随着应用程序的完毕而关闭port,也就是说,Spark应用程序执行完后,将无法查看应用程序的历史 ...

  7. Android Annotations浅析

    这阵子遇到了好多事情,挺久没来更新博文了,这两天在学这个开源框架Android Annotations,用起来感觉挺方便的, 相信用过Spring注解的孩子理解起来应该比較easy! 就是配置起来比較 ...

  8. Linux配置快捷方式路径

    快就一个字,我只说一次! 1.命令顺序: a)       先输入 cd / b)      gedit /etc/profile c)       最后添加路径,看到蓝色部分的没? 把/home更换 ...

  9. C/C++中float和double的存储结构(转)

    在C/C++中float是32位的,double是64位的,两者在内存中的存储方式和能够表示的精度均不同,目前C/C++编译器标准都遵照IEEE制定的浮点数表示法来进行float,double运算. ...

  10. javascript的基本类型剖析:

    javascript的基本数据类型包含 string,number,boolean,function,object,undified基本的6的基本数据类型 这篇文章就主要介绍一下这六种基本数据类型的主 ...