为什么thread_local不能应用于非静态数据成员以及如何实现线程局部非静态数据成员?

时间:2015-09-03 01:31:20

标签: c++ multithreading c++11 openmp thread-local-storage

为什么thread_local不能应用于非静态数据成员?接受this question的答案是:"制作非静态结构或类成员线程本地没有意义。"老实说,我看到很多很好的理由让非静态数据成员成为线程本地的。

假设我们有某种ComputeEngine,其成员函数computeSomething连续多次调用。成员函数内部的一些工作可以并行完成。为此,每个线程都需要某种ComputeHelper,例如,它提供辅助数据结构。所以我们真正想要的是以下内容:

class ComputeEngine {
 public:
  int computeSomething(Args args) {
    int sum = 0;
    #pragma omp parallel for reduction(+:sum)
    for (int i = 0; i < MAX; ++i) {
      // ...
      helper.xxx();
      // ...
    }
    return sum;
  }
 private:
  thread_local ComputeHelper helper;
};

不幸的是,这段代码无法编译。我们可以做的是:

class ComputeEngine {
 public:
  int computeSomething(Args args) {
    int sum = 0;
    #pragma omp parallel
    {
      ComputeHelper helper;
      #pragma omp for reduction(+:sum)
      for (int i = 0; i < MAX; ++i) {
        // ...
        helper.xxx();
        // ...
      }
    }
    return sum;
  }
};

但是,这将在ComputeHelper的连续调用之间构造和销毁computeSomething。假设构造ComputeHelper是昂贵的(例如,由于巨大向量的分配和初始化),我们可能希望在连续调用之间重用ComputeHelper。这引出了以下样板方法:

class ComputeEngine {
  struct ThreadLocalStorage {
    ComputeHelper helper;
  };
 public:
  int computeSomething(Args args) {
    int sum = 0;
    #pragma omp parallel
    {
      ComputeHelper &helper = tls[omp_get_thread_num()].helper;
      #pragma omp for reduction(+:sum)
      for (int i = 0; i < MAX; ++i) {
        // ...
        helper.xxx();
        // ...
      }
    }
    return sum;
  }
 private:
  std::vector<ThreadLocalStorage> tls;
};
  1. 为什么thread_local不能应用于非静态数据成员?什么 这种限制背后的理由是什么?我没有给予好的 线程局部非静态数据成员完美的示例 感?
  2. 实现线程局部非静态的最佳实践是什么 数据成员?

2 个答案:

答案 0 :(得分:4)

至于为什么thread_local不能应用于非静态数据成员,它会破坏这些成员的通常排序保证。也就是说,单个public/private/protected组中的数据成员必须按照与类声明中相同的顺序布置在内存中。更不用说如果在堆栈上分配一个类会发生什么 - TLS成员不会进入堆栈。

至于如何解决这个问题,我建议使用boost::thread_specific_ptr。您可以将其中一个放入您的课程中,并获得您想要的行为。

答案 1 :(得分:2)

线程本地存储通常的工作方式是在线程特定的数据结构中只获得一个指针(例如TEB in Windows

只要所有线程局部变量都是静态的,编译器就可以轻松计算这些字段的大小,分配大小的结构,并为每个字段分配一个静态偏移量。

只要你允许非静态字段,整个方案就会变得更加复杂 - 解决它的一种方法是一个额外的间接级别并在每个类中存储一个索引(现在你在类中隐藏字段,而不是意外) 。

他们显然决定让每个应用程序根据需要处理它,而不是在实施者身上提升这种方案的复杂性。