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



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

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在这种情况下是如何工作的?在我看来,它们非常相似

当一个对象被声明为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'torn"; }
    myclass() : _n(0) { std::cout << "def c'torn"; }
    myclass(const myclass & other) : _n(other._n)
    { std::cout << "copy c'torn"; }
    ~myclass() { std::cout << "bye byen"; }
    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 << "Hin";
    }
    std::cout << "nEntering parallel region #1 (using private)n";
#pragma omp parallel private(inst)
    {
        std::cout << "Hin";
    }
    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;
}

给出

的定义

开始main((
的int c'

正在进入并行区域#0(使用firstprivate(
复制c'或

再见
复制c'或

再见
复制c'或

再见
复制c'或

再见

进入并行区域#1(使用私有(
def c'tor

再见
def c'tor

再见
def c'tor

再见
def c'tor

再见

进入并行区域#2(打印全局实例的值并添加线程号(
def c'tor
0
def c'tor
0
def c'tor
0
0

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

即将离开main((
再见
再见

如果复制构造函数执行深度复制(如果您必须编写自己的副本,它应该执行,如果您没有并且具有动态分配的数据,则默认执行(,那么您将获得对象的深度副本。这与private不同,后者不使用现有对象初始化私有副本。

threadprivate的工作方式完全不同。首先,它只适用于全局或静态变量。更重要的是,它本身就是一项指令,不支持其他条款。您可以在某个地方写入threadprivate杂注行,然后在并行块之前写入#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类型(源代码(。这被列为一个错误已经近十年了,但仍未得到完全解决。给出的Visual Studio错误是

错误C3057:"globalClass":当前不支持动态初始化"threadprivate"符号

g++给出的误差是

错误:"globalClass"在首次使用后声明为"threadprivate">

英特尔编译器可用于类。

还有一个注意事项。如果要复制主线程变量的值,可以使用#pragma omp parallel copyin(globalVarName)。请注意,这与我们上面的例子中的类不兼容(因此我省略了它(。

来源:OMP教程:private,firstprivate,threadprivate

最新更新