【问题标题】:Class declaring itself (*this) private to avoid race conditions / quest for threadprivate in gcc abandoned类声明自己 (*this) 私有以避免竞争条件/放弃 gcc 中的 threadprivate
【发布时间】:2019-01-02 23:13:48
【问题描述】:

我想避免并行代码中的竞争条件。问题是我的类包含几个全局变量,为了简单起见,我们只说一个 x 以及我希望并行的 for 循环。实际代码也有一个方法,它接受一个指向类的指针,在这种情况下,它本身作为它的参数,访问更多的全局变量。因此,让整个实例 threadprivate. 我正在使用 OpenMP 可能是有意义的。

一个最小的工作示例是:

#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++ 给出的错误是 error: 'globalClass' declared 首次使用后的 'threadprivate' 英特尔编译器与类一起使用。

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

尝试解决方案

我相信我可以使用private 关键字,但我不确定如何对一个类的整个实例执行此操作,尽管我更喜欢threadprivate 关键字。在第 7 章,this book 中的图 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();
    
    }

因此,根据定义,这段代码应该可以解决问题,但不知何故无法编译。我如何将这段代码放在一起,以实现所需的线程安全并进行编译,同时尊重非英特尔编译器人员不能选择 threadprivate 的约束?

【问题讨论】:

  • 我对这个问题进行了大量研究,似乎很多人都关心它。与其匿名且毫无帮助地投反对票,为什么不提前指出应该改进的地方?
  • 不是反对者,但如果你包含一个 minimal reproducible example 会很有帮助,足以让你认为是一个独立的、正确的代码,应该可以编译但不是。
  • 公平,我稍微改变了问题。
  • 您能解释一下为什么要在并行区域之间保留私有变量吗?我通常可以找到解决方法,而且我几乎不需要线程私有。说明你想做什么。
  • 你确定你没有混淆 C++ 中的 private 对应于类层次结构中的“可见性”和 OpenMP 中的 private 对应于在 per-线程基础?两者都是完全正交的概念。到目前为止,我真的不明白你的问题是什么以及你想要实现的目标......

标签: c++ gcc openmp


【解决方案1】:

这是一个长期缺失的 GCC 功能:

不过,对于当前的 GCC 版本,thread_local 应该可以工作:

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

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

【讨论】:

  • 感谢您指出这一点。我可能会咬紧牙关,为我的一个微不足道的地区声明一个私有变量清单。
【解决方案2】:

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

至于错误,没有输出很难说,但很可能是多方面的:

  1. 不匹配的右大括号。将 { 放在 pragma 行的末尾不会打开块,它需要位于下一行。
  2. 以这种方式私有化封闭类实例是无效的

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

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

但是请注意,整个事情都是私有的,因此这意味着原始副本中的 f 不会被更新,除非您在私有副本上执行 addInt 等效项,然后在原始副本上执行 f[n] 分配。

编辑:我最初提到使用 default(firstprivate) 子句,但是 默认子句只为 FORTRAN 提供私有和第一私有。要获得 在 c++ 中的效果相同,执行上述操作并将构造复制到每个的新实例中, 或默认使用按值捕获的 lambda,然后首先将其私有化,*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();

【讨论】:

  • 'default(firstprivate)' 不起作用,或者至少 CLion 抱怨说它期待“无”或“共享”。我将阅读 OpenMP 文档以了解您关于“可以将私有或 firstprivate 设为默认值”的建议
  • 所以我检查过,你所说的一般只适用于 Fortran,请参阅此处computing.llnl.gov/tutorials/openMP/#DEFAULT 尽管有一个神秘的注释“[h]然而,实际实现可能提供此选项。”
  • 是的,不会编译并给出错误“预期在 'firstprivate' 之前是 'non' 或 'shared'”。我猜 LLNL 的好人指的是 OpenMP 的定制版本?
  • 你说的很对。即使是我们这些帮助写东西的人有时也会犯这些错误。答案已编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-30
  • 2010-09-25
  • 1970-01-01
  • 1970-01-01
  • 2010-09-25
  • 2019-06-12
相关资源
最近更新 更多