在OpenMP上下文中对firstprivate和threadprivate感到困惑

时间:2015-09-02 07:27:33

标签: c++ openmp

假设我在对象中打包了一些资源,然后根据资源执行一些计算。我通常做的是初始化并行区域之外的对象,然后使用 firstprivte 关键字

int main()
{
        // initialize Widget objs
         Widget Widobj{params1,params2,params3...};

        #pragma omp parallel for firstprivate(Widobj)
        for (int i=0; i< N; ++i)
          {
             // computation based on resources in Widobj
          }

}

我认为在这种情况下,每个线程将独立处理Widobj中的资源,我想每个线程都有一个Widobj的副本(可能是一个深层副本,我是对的吗?)。现在我对其他关键字 threadprivate 感到困惑,threadprivate在这种情况下如何工作?在我看来他们非常相似

1 个答案:

答案 0 :(得分:3)

当声明对象firstprivate时,将调用复制构造函数,而使用private时,将调用默认构造函数。我们将在下面提到threadprivate。证明(英特尔C ++ 15.0):

#include <iostream>
#include <omp.h>

class myclass {
    int _n;
public:
    myclass(int n) : _n(n) { std::cout << "int c'tor\n"; }

    myclass() : _n(0) { std::cout << "def c'tor\n"; }

    myclass(const myclass & other) : _n(other._n)
    { std::cout << "copy c'tor\n"; }

    ~myclass() { std::cout << "bye bye\n"; }

    void print() { std::cout << _n << "\n"; }

    void add(int t) { _n += t; }
};

myclass globalClass;

#pragma omp threadprivate (globalClass)

int main(int argc, char* argv[])
{
    std::cout << "\nBegninning main()\n";

    myclass inst(17);

    std::cout << "\nEntering parallel region #0 (using firstprivate)\n";
#pragma omp parallel firstprivate(inst)
    {
        std::cout << "Hi\n";
    }

    std::cout << "\nEntering parallel region #1 (using private)\n";
#pragma omp parallel private(inst)
    {
        std::cout << "Hi\n";
    }

    std::cout << "\nEntering parallel region #2 (printing the value of "
                    "the global instance(s) and adding the thread number)\n";
#pragma omp parallel
    {
        globalClass.print();
        globalClass.add(omp_get_thread_num());
    }

    std::cout << "\nEntering parallel region #3 (printing the global instance(s))\n";
#pragma omp parallel
    {
        globalClass.print();
    }

    std::cout << "\nAbout to leave main()\n";
    return 0;
}

给出

  

def c&#39; tor

     

begninning main()
  int c&#39; tor

     

输入并行区域#0(使用firstprivate)
  复制c&#39; tor   嗨
  再见
  复制c&#39; tor   嗨
  再见
  复制c&#39; tor   嗨
  再见
  复制c&#39; tor   嗨
  再见

     

输入并行区域#1(使用私有)
  def c&#39; tor   嗨
  再见
  def c&#39; tor   嗨
  再见
  def c&#39; tor   嗨
  再见
  def c&#39; tor   嗨
  再见

     

输入并行区域#2(打印全局实例的值并添加线程编号)
  def c&#39; tor   0
  def c&#39; tor   0
  def c&#39; tor   0
  0

     

输入并行区域#3(打印全局实例)
  0
  1
  2
  3

     

即将离开main()
  再见
  再见

如果复制构造函数执行深层复制(如果您必须编写自己的复制,并且默认情况下,如果您没有并且已动态分配数据),那么您将获得对象的深层副本。这与使用现有对象初始化私有副本的private相反。

threadprivate完全不同。首先,它仅适用于全局或静态变量。更重要的是,它本身就是一个指令,并且不支持任何其他条款。您可以在某处编写threadprivate pragma行,然后在并行块之前写#pragma omp parallel。还有其他差异(在内存中存储对象等),但这是一个良好的开端。

让我们分析一下上面的输出。 首先,请注意,在进入区域#2时,将调用默认构造函数,为线程创建一个私有的新全局变量。这是因为在进入第一个平行区域时,全局变量的并行副本还不存在。

接下来,由于NoseKnowsAll考虑了最重要的区别,因此线程私有全局变量通过不同的并行区域持久存在。在区域#3中没有构造,我们看到保留了来自区域#2的添加的OMP线程号。另请注意,在区域2和3中没有调用析构函数,而是在离开main()之后(由于某种原因只有一个(主)副本) - 另一个是inst。这可能是一个错误.. )。

这让我们了解了使用英特尔编译器的原因。 Visual Studio 2013以及g ++(我的计算机上的4.6.2,Coliru (g++ v5.2)codingground (g++ v4.9.2))仅允许POD种类型(source)。这被列为近十年的错误,但仍未完全解决。 给出的Visual Studio错误是

  

错误C3057:&#39; globalClass&#39; :动态初始化&#39; threadprivate&#39;目前不支持符号

并且g ++给出的错误是

  

错误:&#39; globalClass&#39;声明&#39; threadprivate&#39;首次使用后

英特尔编译器适用于类。

还有一点需要注意。如果要复制主线程变量的值,可以使用#pragma omp parallel copyin(globalVarName)。请注意,这样做可以使用类,如上例所示(因此我将其删除了)。

来源:OMP tutorialprivatefirstprivatethreadprivate