Класс объявляет себя (* this) закрытым, чтобы избежать условий гонки / квест для threadprivate в gcc отменен - PullRequest
0 голосов
/ 03 января 2019

Я хочу избежать состояния гонки в параллельном коде. Проблема в том, что мой класс содержит несколько глобальных переменных, скажем, просто одну x для простоты, а также цикл for, который я хочу сделать параллельным. Фактический код также имеет метод, который принимает указатель на класс, в данном случае сам в качестве аргумента, для доступа к еще большему количеству глобальных переменных. Так что может иметь смысл сделать весь экземпляр threadprivate. Я использую OpenMP. MWE составляет

#include <iostream>
#include <omp.h>
class lotswork {
public:
    int x;
    int f[10];

    lotswork(int i = 0) { x = i; };

    void addInt(int y) { x = x + y; }

    void carryout(){

        #pragma omp parallel for
        for (int n = 0; n < 10; ++n) {
            this->addInt(n);
            f[n] = x;
        }
        for(int j=0;j<10;++j){
            std::cout << " array at " << j << " = " << f[j] << std::endl;
        }
        std::cout << "End result = " << x << std::endl;
    }
};



int main() {
    lotswork production(0);
    #pragma omp threadprivate(production)
    production.carryout();

}

Мой вопрос: как я могу это сделать? Использование ключевого слова threadprivate возвращает следующее сообщение об ошибке компилятора: error: ‘production’ declared ‘threadprivate’ after first use Я думаю, что эта проблема компилятора здесь еще не была решена :

Это подводит нас к тому, почему я использовал компилятор Intel. Visual Studio 2013 as а также g ++ (4.6.2 на моем компьютере, Coliru (g ++ v5.2), область программирования (g ++ v4.9.2)) разрешать только типы POD (источник). Это указано как ошибка в течение почти десятилетия и до сих пор не полностью решены. Визуальный Ошибка студии: C3057: globalClass: динамический инициализация символов 'threadprivate' в настоящее время не поддерживается и ошибка, заданная g ++, является ошибкой: объявлено 'globalClass' 'threadprivate' после первого использования Компилятор Intel работает с классами.

К сожалению, у меня нет доступа к компилятору Intel, но я использую GCC 8.1.0. Я провел небольшое предварительное исследование и нашел обсуждение этого здесь , но этот путь остыл, десять лет назад. Я задаю этот вопрос, потому что несколько человек имели проблемы с этим и решили их, либо объявив указатель класса как здесь , либо предложив ужасные обходные пути . Последний подход кажется ошибочным, потому что указатель обычно объявляется как константа, но тогда у нас есть threadprivate указатели, в то время как экземпляр все еще используется совместно.

Попытка решения

Я считаю, что могу использовать ключевое слово private, но не уверен, как это сделать со всем экземпляром класса, хотя я бы предпочел ключевое слово threadprivate. Пример, аналогичный моему выше, на котором я смоделировал мой MWE, также обсуждался в главе 7, рис. 7.17 в этой книги , но без решения. (Я хорошо осведомлен о состоянии гонки и о том, почему это проблема.)

При необходимости я могу подтвердить, что вывод вышеуказанной программы без каких-либо дополнительных ключевых слов является недетерминированным.

Еще одна попытка решения

Теперь я подумал о решении, но по какой-то причине оно не скомпилируется. С точки зрения безопасности потоков и логической точки зрения моя проблема должна быть решена с помощью следующего кода. Тем не менее, должна быть какая-то ошибка.

#include <iostream>
#include <omp.h>
class lotswork : public baseclass {
public:
    int x;
    int f[10];

    lotswork(int i = 0) { x = i; };

    void addInt(int y) { x = x + y; }

        void carryout(){
    //idea is to declare the instance private
    #pragma omp parallel firstprivate(*this){
    //here, another instance of the base class will be instantiated which is inside the parallel region and hence automatically private
    baseclass<lotswork> solver;

  #pragma omp for
  for (int n = 0; n < 10; ++n) 
      {
          this->addInt(n);
          f[n] = x;
          solver.minimize(*this,someothervariablethatisprivate);
      }
                                             } //closing the pragma omp parallel region
                for(int j=0;j<10;++j){
                    std::cout << " array at " << j << " = " << f[j] << std::endl;
                }
                std::cout << "End result = " << x << std::endl;
            }
        };



    int main() {
        lotswork production(0);
        #pragma omp threadprivate(production)
        production.carryout();

    }

Таким образом, этот код, основанный на определениях, должен добиться цели, но каким-то образом он не компилируется. Может кто-нибудь помочь мне собрать этот код вместе, чтобы он достиг желаемой безопасности потока и компилировал, соблюдая ограничение, что threadprivate не подходит для людей, не являющихся разработчиками Intel? Заранее большое спасибо за помощь.

Ответы [ 2 ]

0 голосов
/ 11 января 2019

Кажется, здесь есть некоторая путаница с конструкциями OpenMP. threadprivate используется, как и thread_local, для создания для каждого потока копии объекта со статическим временем жизни, глобальной или статической переменной. Как уже отмечалось, с этим есть некоторые проблемы с реализацией, но даже если реализации могут обрабатывать класс, использование threadprivate в нестатической локальной переменной приведет к ошибке.

Что касается ошибки, трудно сказать без вывода, но, скорее всего, это несколько вещей:

  1. Непревзойденная закрывающая скобка. Размещение { в конце строки прагмы не открывает блок, оно должно быть на следующей строке.
  2. Недопустимо приватизировать экземпляр включающего класса таким образом

Если вам нужно создать закрытую копию включающего класса в каждом потоке, это можно сделать путем копирования класса в переменную, объявленную внутри параллельной области:

#pragma omp parallel
{
  lotswork tmp(*this);
  // do things with private version
}

Обратите внимание, однако, что все это личное, так что это означает, что f в оригинальной копии не будет обновляться, если вы не выполните addInt эквивалентов для всех частных копий, а затем для f[n] назначений для оригинала.

Edit: я первоначально упомянул, используя предложение default(firstprivate), но Предложение по умолчанию предлагает только приватный и первый приватный для FORTRAN. Чтобы получить тот же эффект в C ++, сделать выше и скопировать конструкцию в новый экземпляр каждого, или используйте лямбду с захватом по значению по умолчанию, затем сначала укажите, *this для работы требуется c ++ 17, но он выполняет именно то, что запрашивает:

auto fn = [=,*this](){
  // do things with private copies
  // all updates to persist in shared state through pointers
};
#pragma omp parallel firstprivate(fn)
fn();
0 голосов
/ 05 января 2019

Это давно отсутствующая функция GCC:

С токомВерсии GCC, ожидается, что thread_local будет работать, хотя:

int main() {
  thread_local lotswork production(0);
  production.carryout();
}

Однако, я не думаю, что это будет работать в вашем случае, потому что параллельный цикл в carryout все еще будет работать на одном lotswork пример.Я считаю, что это применимо и к исходному коду, использующему threadprivate.Возможно, вам нужно переместить параллельный цикл за пределы функции-члена carryout.

...