假设我已经将一些资源打包在一个对象中,然后根据这些资源执行一些计算。我通常要做的是初始化并行区域之外的对象,然后使用 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如何工作?在我看来,它们非常相似

最佳答案

当将一个对象声明为firstprivate时,将调用复制构造函数,而当使用private时,将调用默认构造函数。我们将在下面解决threadprivate。证明(Intel 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;
}





如果复制构造函数进行了深层复制(如果必须编写自己的文件,则应进行深层复制;如果没有,并且已动态分配数据,则默认情况下应进行复制),那么您将获得对象的深层复制。这与private相反,后者不使用现有对象初始化私有(private)副本。
threadprivate的工作原理完全不同。首先,它仅适用于全局或静态变量。更关键的是,它本身就是一个指令,不支持其他子句。您将threadprivate杂注行写在某个地方,然后在并行块之前写#pragma omp parallel。还有其他区别(对象存储在内存中的位置等),但这是一个好的开始。

让我们分析以上输出。
首先,请注意,在进入区域2时,默认构造函数被称为创建线程专有的新全局变量。这是因为在进入第一个并行区域时,尚不存在全局变量的并行副本。

接下来,由于NoseKnowsAll认为最关键的区别,线程私有(private)全局变量通过不同的并行区域保持不变。在区域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)。这已被列为bug了将近十年,但仍未得到充分解决。
给出的Visual Studio错误是



而g++给出的错误是



英特尔编译器可用于类。

还有一张便条。如果要复制主线程变量的值,可以使用 #pragma omp parallel copyin(globalVarName) 。请注意,这不适用于上面的示例中的类(因此我省略了)。

来源:OMP tutorial:privatefirstprivatethreadprivate

08-27 02:13