假设我已经将一些资源打包在一个对象中,然后根据这些资源执行一些计算。我通常要做的是初始化并行区域之外的对象,然后使用 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:private,firstprivate,threadprivate