Confused about firstprivate и threadprivate в контексте 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 } } 

И я думаю, что в этом случае каждый stream будет иметь дело с ресурсом в Widobj независимо, и я полагаю, что каждый stream будет иметь копию Widobj (возможно, глубокую копию, я прав?). Теперь я запутался в другом ключевом слове threadprivate , как работает threadprivate в этом контексте? Мне кажется, они очень похожи

Когда объект объявляется firstprivate , firstprivate конструктор копирования, тогда как при использовании private используется конструктор по умолчанию. Ниже мы рассмотрим threadprivate . Доказательство (Intel C ++ 15.0):

 #include  #include  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'tor

Начальное основное ()
int c'tor

Ввод параллельной области # 0 (с использованием firstprivate)
копировать c'tor
Привет
пока-пока
копировать c'tor
Привет
пока-пока
копировать c'tor
Привет
пока-пока
копировать c'tor
Привет
пока-пока

Вход в параллельный регион №1 (с использованием частного)
def c'tor
Привет
пока-пока
def c'tor
Привет
пока-пока
def c'tor
Привет
пока-пока
def c'tor
Привет
пока-пока

Ввод параллельной области # 2 (печать значения глобального экземпляра (ов) и добавление номера streamа)
def c'tor
0
def c'tor
0
def c'tor
0
0

Ввод параллельной области # 3 (печать глобальных экземпляров)
0
1
2
3

О выходе из main ()
пока-пока
пока-пока

Если конструктор копирования выполняет глубокую копию (что необходимо, если вам нужно написать свой собственный, а по умолчанию - по умолчанию, если нет, и у вас есть динамически распределенные данные), вы получаете глубокую копию своего объекта. Это в отличие от private который не инициализирует частную копию существующим объектом.

threadprivate работает совершенно по-другому. Для начала, это только для глобальных или статических переменных. Еще более критично, это сама по себе директива и не поддерживает никаких других положений. Вы пишете threadprivate прагма threadprivate где-то, а затем #pragma omp parallel перед параллельным блоком. Существуют и другие отличия (где в памяти хранится объект и т. Д.), Но это хорошее начало.

Давайте проанализируем вышеприведенный вывод. Во-первых, обратите внимание, что при вводе области №2 конструктор по умолчанию называется созданием новой глобальной переменной, приватной для streamа. Это связано с тем, что при входе в первую параллельную область параллельная копия глобальной переменной еще не существует.

Далее, поскольку NoseKnowsAll считает наиболее важным отличием, частные глобальные переменные streamа сохраняются в разных параллельных областях. В области №3 нет конструкции, и мы видим, что добавленный номер streamа OMP из области №2 сохраняется. Также обратите внимание, что ни один деструктор не вызывается в регионах 2 и 3, а, скорее, после выхода из main() (и только одна ( main() ) копия по какой-либо причине - другая - inst . Это может быть ошибка ...).

Это приводит нас к тому, почему я использовал компилятор Intel. Visual Studio 2013, а также g ++ (4.6.2 на моем компьютере, Coliru (g ++ v5.2) , codingground (g ++ v4.9.2) ) допускают только типы POD ( источник ). Это указано как ошибка в течение почти десятилетия и до сих пор не полностью решена. Указанная ошибка Visual Studio

ошибка C3057: «globalClass»: динамическая инициализация символов «threadprivate» в настоящее время не поддерживается

и ошибка, заданная g ++, равна

error: «globalClass» объявляет «threadprivate» после первого использования

Компилятор Intel работает с classами.

Еще одно замечание. Если вы хотите скопировать значение переменной главного streamа, вы можете использовать #pragma omp parallel copyin(globalVarName) . Обратите внимание, что это не работает с classами, как в нашем примере выше (следовательно, я его оставил).

Источники: учебник OMP : private , firstprivate , threadprivate