转自: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. JAVA基础(10)——IO、NIO

    转载:http://blog.csdn.net/weitry/article/details/52964948 JAVA基础系列规划: JAVA基础(1)——基本概念 JAVA基础(2)——数据类型 ...

  2. Python工作日类库Busines Holiday介绍

    引言: 在日常工作中.常常会碰到相似的场景.须要计算在某个时间段内的工作日以及确定某天是否为工作日,这里的介绍的工具包将很好的解决问题. 1. 工具包Business Holiday介绍 其提供了很e ...

  3. escape(s, t)函数的实现

    https://item.taobao.com/item.htm? spm=686.1000925.0.0.9TTLHO&id=535006878999 <span style=&quo ...

  4. java中Runtime类

    一.概述      Runtime类封装了运行时的环境.每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接.      一般不能实例化一个Runtime对象, ...

  5. HDOJ 3944 DP?

    尽量沿着边走距离最短.化减后 C(n+1,k)+ n - k, 预处理阶乘,Lucas定理组合数取模 DP? Time Limit: 10000/3000 MS (Java/Others)    Me ...

  6. 学 Vim 时希望早点知道的建议

    来自wechat 从 2009 年开始,我就一直把 Vim 当做我的主要(唯一)文本编辑器.在过去的这些年,我学到了很多好用的 Vim 技巧,它们令我感觉相见恨晚,因为它们极大地提高了我的文本编辑效率 ...

  7. case when then 中的条件里面加上 and or 的处理方式

    错误的方式: end 正确1: end 正确2,去掉括号: end

  8. 使用 Gradle 实现一套代码开发多个应用

    代码地址如下:http://www.demodashi.com/demo/11297.html 在文章 使用 Gradle 对应用进行个性化定制 中,我们能够针对一个应用的正式服.测试服.超管服等其他 ...

  9. 自己动手,制作真正的.net Framework 3.5 SP1离线安装包(转)

    .NET Framework 3.5 SP1发布了,兴冲冲…下载,当然是完全离线安装包.net Framework 3.5 Service pack 1(Full Package) 231MB,网络慢 ...

  10. 人工智能 VS 机器学习 VS 深度学习

    (原文:) The Difference Between AI, Machine Learning, and Deep Learning? (译文:) 人工智能 . 机器学习 和 深度学习的区别? 作 ...