类声明自己(* this)为私有,以避免竞争条件/放弃的gcc寻求线程私有

时间:2019-01-02 23:13:48

标签: c++ gcc openmp

我想避免并行代码中的竞争条件。问题是我的类包含多个全局变量,为简单起见,我们只说一个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 我认为这里的编译器问题仍然没有solved

  

这使我们了解了为什么我使用英特尔编译器。 Visual Studio 2013如   以及g ++(我计算机上的4.6.2,Coliru(g ++ v5.2),codingground   (g ++ v4.9.2))仅允许POD类型(源)。这被列为错误   近十年来,仍然没有得到充分解决。视觉   指定的Studio错误为错误C3057:“ globalClass”:动态   当前不支持“ threadprivate”符号的初始化   并且g ++给出的错误是错误:声明了'globalClass'   首次使用后为“ threadprivate” Intel编译器可用于类。

不幸的是,我无法访问Intel的编译器,但使用GCC 8.1.0。我做了一些背景研究,并发现了关于此here的讨论,但是十年前,这条路一直很冷。我问这个问题是因为有几个人对此有疑问,并通过将类指针声明为here或提出了可怕的workarounds来解决了这个问题。后一种方法似乎是错误的,因为通常将指针声明为常量,但是当实例仍然共享时,我们就有threadprivate指针。

尝试解决方案

我相信我可以使用private关键字,但是尽管我更喜欢threadprivate关键字,但不确定如何在整个类实例中执行此操作。 this book的第7章,图7.17中也讨论了与上面类似的我在其上建模MWE的示例,但没有解决方案。 (我很清楚比赛条件及其原因。)

如有必要,我可以提供证据证明上述程序的输出(没有任何额外的关键字)是不确定的。

另一种尝试解决方案

我现在想到了一个解决方案,但是由于某种原因,它无法编译。从线程安全和逻辑的角度来看,我的问题应通过以下代码解决。但是,肯定会有某种错误。

#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();

    }

因此,基于定义的这段代码应该可以解决问题,但无法以某种方式编译。有人可以帮助我将这段代码放在一起,以实现所需的线程安全性和编译,同时遵守非Intel编译器人员无法选择threadprivate的约束吗?提前非常感谢您的帮助。

2 个答案:

答案 0 :(得分:4)

这是长期缺少的GCC功能:

在当前的GCC版本中,thread_local可以正常运行,

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

但是,我认为这不适用于您的情况,因为carryout中的并行循环仍将在单个lotswork实例上运行。我相信这也将适用于使用threadprivate的原始代码。您可能需要将并行循环移出carryout成员函数之外。

答案 1 :(得分:4)

这里似乎对OpenMP构造有些困惑。 threadprivate与thread_local一样,用于为静态生存期的对象(全局变量或静态变量)创建每个线程的副本。如前所述,这存在一些实现问题,但是即使实现可以处理该类,在非静态局部变量上使用threadprivate也会产生错误。

对于错误,很难说没有输出,但是可能有多种原因:

  1. 无与伦比的右括号。将{放在编译指示行的末尾不会打开一个块,它必须在下一行。
  2. 以这种方式私有化封闭的类实例是无效的

如果需要在每个线程中创建封闭类的私有副本,则可以通过将类复制构造为在并行区域内声明的变量来实现:

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

但是请注意,整个事情都是私有的,因此这意味着原始副本中的f不会被更新,除非您先对私有副本执行addInt等效项,然后再执行{{1} }原始作业。

编辑:我最初提到使用f[n]子句,但是 default子句仅为FORTRAN提供private和first private。为了得到 在c ++中具有相同的效果,请执行上述操作并将构造复制到每个实例的新实例中, 或使用默认情况下按值捕获的lambda,然后先对其进行私有default(firstprivate) 需要c ++ 17才能正常工作,但确实满足要求:

*this