我的资源需要在两次访问之间保持其状态。使用OpenMP将程序并行化时,我想确保每个线程都有自己的副本,并且实例不会被破坏并为每个并行区域重新创建。为此,我使用了一个全局变量threadprivate。下面,我有一个简单的测试用例,应说明设置。

我有两个问题:


是否保证在程序执行期间每个线程仅创建一次资源(在obj下面)?
当我在四个线程上运行示例程序时,每个线程报告“ Obj created ...”和“ State set to ...”,但只有线程零报告“ Obj destroy ...”。这里发生了什么?




#ifdef _OPENMP
#include <omp.h>
#endif
#include <vector>
#include <iostream>
#include <iomanip>

class obj {
public:
  obj() : state(0) {
    res = new int [100];
#pragma omp critical
    {
      std::cout << "Obj created, state " << state;
#ifdef _OPENMP
      std::cout << ", thread " << omp_get_thread_num();
#endif
      std::cout << std::endl;
    }
  }

  ~obj() {
    delete[] res;
#pragma omp critical
    {
    std::cout << "Obj destroyed, state " << state;
#ifdef _OPENMP
      std::cout << ", thread " << omp_get_thread_num();
#endif
      std::cout << std::endl;

    }
  }

  void init(int set) {
    state = set;
#pragma omp critical
    {
      std::cout << "State set to " << state;
#ifdef _OPENMP
      std::cout << ", thread " << omp_get_thread_num();
#endif
      std::cout << std::endl;
    }
  }

  int operator()() {
    return ++state;
  }

private:
  int state;
  int* res;
};

extern obj obj1;
#pragma omp threadprivate(obj1)
obj obj1;

void init() {
#ifdef _OPENMP
#pragma omp parallel
  {
  obj1.init(100 * omp_get_thread_num());
  }
#else
  obj1.init(100);
#endif
}

void work() {
  std::cout << "Computing" << std::endl;

  int constexpr length = 20;
  std::vector<int> vec(length);

#pragma omp parallel for
  for (int idx = 0; idx < length; idx++) {
    vec[idx] = obj1();
  }

  std::cout.fill('0');

  for (auto const & e: vec) {
    std::cout << std::setw(3) << e << ' ';
  }
  std::cout << std::endl;
}

int main() {
  init();
  work();
  work();
  work();
}

最佳答案

在以下情况下,threadprivate可以正常工作


每次声明变量后都出现#pragma omp threadprivate
必须使用omp_set_dynamic(false)关闭动态线程(默认为实现定义)。


请参见示例here

我完全不会依赖析构函数。 OpenMP有很多未指定的地方,编译器可能会对其进行优化。

这是OpenMP Spec(v4.0 p.12.14.2)的摘录


  根据基本语言如何处理静态变量,但在程序中未指定的位置,释放了线程私有变量的所有副本的存储。





  未指定用于类类型的不同线程专用C ++变量的任何析构函数的调用顺序。


threadprivate的更多信息
(v4.0 p2.4.12)


  threadprivate变量的每个副本都按照程序指定的方式初始化一次,但是在第一次引用该副本之前在程序中的未指定点初始化。根据基本语言处理静态变量的方式,释放了threadprivate变量的所有副本的存储,但是在程序中未指定的位置。
  
  线程引用另一个线程的threadprivate变量副本的程序不合格。
  
  如果正在执行的线程切换到另一个修改变量的任务,则threadprivate变量的内容可以在任务调度点之间变化。有关任务计划的更多详细信息,请参阅第14页的第1.3节和第113页的2.11节。
  
  在parallel区域中,主线程将引用该线程中遇到parallel区域的变量的副本。
  
  在顺序零件过程中,将引用初始线程的变量副本。保证初始线程的threadprivate变量副本中的数据值在程序中对变量的两个连续引用之间保持不变。
  
  仅在满足以下所有条件的情况下,才能确保非初始线程的threadprivate变量中的数据值在两个连续的活动parallel区域之间持久存在:
  
  
  两个parallel区域都不嵌套在另一个显式的parallel区域内。
  用于执行两个parallel区域的线程数是相同的。
  用于执行两个parallel区域的线程相似性策略是相同的。
  封闭的任务区域中dyn-var内部控制变量的值在进入两个parallel区域时为false。
  
  
  如果所有这些条件都成立,并且在两个区域中均引用了threadprivate变量,则在各自区域中具有相同线程号的线程将引用该变量的相同副本。

10-07 22:45