[转]OpenMP中的private/firstprivate/lastprivate/threadprivate之间的比较

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

[cpp] view plain copy

print?

  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. 在并行区域之前定义的原来的变量,在并行区域后也是”未定义“的。

[cpp] view plain copy

print?

  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变量和并行区域外同名的变量没有存储关联

[cpp] view plain copy

print?

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

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

[cpp] view plain copy

print?

  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.

分析下面的例子:

[cpp] view plain copy

print?

  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的例子很类似:

[cpp] view plain copy

print?

  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.

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

[cpp] view plain copy

print?

  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的作用:

[cpp] view plain copy

print?

  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的例子很类似:

[cpp] view plain copy

print?

  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的,下面的例子即可看出:

[cpp] view plain copy

print?

  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的使用:

[cpp] view plain copy

print?

  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是针对全局的变量的。

时间: 2024-10-12 22:52:43

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

c++中的private/protect/public &amp; virtual

private: 私有控制符.这类成员只能被本类中的成员函数和类的友元函数访问. protected: 受保护控制符.这类成员可以被本类中的成员函数和类的友元函数访问,也可以被派生类的成员函数和类的友元函数访问. public: 共有控制符.这类成员可以被本类中的成员函数和类的友元函数访问,也可以被类作用域内的其他函数引用. virtual: C++通过虚函数实现多态."无论发送消息的对象属于什么类,它们均发送具有同一形式的消息,对消息的处理方式可能随接手消息的对象而变"的处理方式被称

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

一,public,private,protected的区别 public:权限是最大的,可以内部调用,实例调用等. protected: 受保护类型,用于本类和继承类调用. private: 私有类型,只有在本类中使用. 二,实例 查看复制打印? <?php error_reporting(E_ALL); class test{ public $public; private $private; protected $protected; static $instance; public fun

【C++基础】 类中static private public protected

静态成员在一个类的所有实例间共享数据 “类属性”,是描述类的所有对象共同特征的一个数据项,对所有对象,它的值相同,static定义,为整个类所共有.相对于“实例属性” 如果static成员是私有类型,则只可通过共有静态成员函数访问 [注意]类中仅对成员进行声明而不初始化,必须在文件作用域的某个地方初始化 在class和main外即全局变量域,int Test::ite=0 ;   public private protected 派生类可访问的类型 可以 可以 可以         [C++基础

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

本文参考<OpenMP中的任务调度>博文,主要讲的是OpenMP中的schedule子句用法. 一.应用需求 在OpenMP并行计算中,任务调度主要用于并行的for循环.当for循环中每次迭代的计算量相差较大时,如果简单的为每次迭代分配相同的线程,就会导致线程任务不均衡,CPU资源没有被充分利用,影响程序执行性能.例如下面这种情况: int i, j; int a[100][100] = {0}; for ( i = 0; i < 100; ++i ) { for( j = i; j &

alert日志中出现Private Strand Flush Not Complete的处理方法

还是南京那个客户的库,alert.log日志还报了如下的错误: Fri Oct 17 19:59:51 2014 Thread 1 cannot allocate new log, sequence 4722 Private strand flush not complete Current log# 1 seq# 4721 mem# 0: /oradata/sgomp5/redo01.log Thread 1 advanced to log sequence 4722 (LGWR switch

OpenMP 中的线程任务调度

OpenMP中任务调度主要针对并行的for循环,当循环中每次迭代的计算量不相等时,如果简单地给各个线程分配相同次数的迭代,则可能会造成各个线程计算负载的不平衡,影响程序的整体性能. 如下面的代码中,如果每个线程执行的任务数量平均分配,有的线程会结束早,有的线程结束晚: 1 #include<stdio.h> 2 #include<omp.h> 3 4 int main(){ 5 int a[100][100] = {0}; 6 #pragma omp parallel for 7

C#中父窗口和子窗口之间控件互操作实例

本文实例讲述了C#中父窗口和子窗口之间控件互操作的方法.分享给大家供大家参考.具体分析如下: 很多人都苦恼于如何在子窗体中操作主窗体上的控件,或者在主窗体中操作子窗体上的控件.相比较而言,后面稍微简单一些,只要在主窗体中创建子窗体的时候,保留所创建子窗体对象即可. 下面重点介绍前一种,目前常见的有两种方法,基本上大同小异: 第一种,在主窗体类中定义一个静态成员,来保存当前主窗体对象,例如: 代码如下: public static yourMainWindow pCurrentWin = null

VC中利用多线程技术实现线程之间的通信

文章来源:[url]http://www.programfan.com/article/showarticle.asp?id=2951[/url] 当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力.用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义.现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的.

简单理解javascript中的原型对象,实现对之间共享属性和行为

javascript中提供了构造函数,能够方便的创建对象.典型的构造函数如下: function Person(name, age) { this.name = name; this.age = age; this.say = function () { return this.name + ',' + this.age;; } 之后就可以用new和构造函数创建多个对象.javascript中,类的不同对象之间,属性和方法都是独立的.什么意思呢?java中类的不同对象之间,成员变量是独立的(每个