我的编译器是否忽略了我未使用的静态thread_local类成员?

时间:2019-11-04 09:38:46

标签: c++ compiler-optimization thread-local-storage

我想在我的课程中进行一些线程注册,所以我决定为thread_local功能添加一项​​检查:

#include <iostream>
#include <thread>

class Foo {
 public:
  Foo() {
    std::cout << "Foo()" << std::endl;
  }
  ~Foo() {
    std::cout << "~Foo()" << std::endl;
  }
};

class Bar {
 public:
  Bar() {
    std::cout << "Bar()" << std::endl;
    //foo;
  }
  ~Bar() {
    std::cout << "~Bar()" << std::endl;
  }
 private:
  static thread_local Foo foo;
};

thread_local Foo Bar::foo;

void worker() {
  {
    std::cout << "enter block" << std::endl;
    Bar bar1;
    Bar bar2;
    std::cout << "exit block" << std::endl;
  }
}

int main() {
  std::thread t1(worker);
  std::thread t2(worker);
  t1.join();
  t2.join();
  std::cout << "thread died" << std::endl;
}

代码很简单。我的Bar类具有一个静态thread_local成员foo。如果创建了静态thread_local Foo foo,则意味着创建了一个线程。

但是,当我运行代码时,Foo()中什么也不会打印,并且如果我在Bar的使用foo的构造函数中删除注释,则代码可以正常工作。 / p>

我在GCC(7.4.0)和Clang(6.0.0)上进行了尝试,结果是相同的。 我猜编译器发现foo未使用,并且没有生成实例。所以

  1. 编译器是否忽略了static thread_local成员?我怎么能够 为此调试吗?
  2. 如果是这样,为什么普通的static成员没有这个问题?

2 个答案:

答案 0 :(得分:8)

您的观察没有问题。 [basic.stc.static]/2禁止消除具有静态存储持续时间的变量:

  

如果具有静态存储持续时间的变量具有初始化或   具有副作用的析构函数,即使将其消除   似乎未使用,除非类对象或其复制/移动可能   按照[class.copy]中指定的方式消除。

此限制在其他存储期限内不存在。实际上,[basic.stc.thread]/2说:

  

具有线程存储持续时间的变量应在初始化之前   它的第一个odr用途,并且如果已建造,应在   线程退出。

这表明除非使用odr,否则无需构造具有线程存储持续时间的变量。


但是为什么会有这种差异?

对于静态存储持续时间,每个程序只有一个变量实例。其构造的副作用可能非常明显(有点像程序范围的构造函数),因此需要这些副作用。

但是,对于线程本地存储持续时间,存在一个问题:算法可能启动很多线程。对于大多数这些线程,变量是完全不相关的。如果一个调用std::reduce(std::execution::par_unseq, first, last)的外部物理仿真库最终创建了许多foo实例,对吗?

当然,合理使用未本地使用的线程本地存储持续时间的变量(例如,线程跟踪器)会产生副作用。但是,保证的优势不足以弥补上述缺陷,因此只要不使用这些变量,就可以消除它们。 (不过,您的编译器可以选择不这样做。您也可以在std::thread周围做自己的包装器,以解决此问题。)

答案 1 :(得分:1)

我在“ ELF Handling For Thread-Local Storage”中找到了此信息,可以证明@ L.F。的答案

此外,运行时支持应避免创建 线程本地存储(如果不需要)。例如, 该模块只能由组成该线程的许多线程中的一个线程使用 处理。分配内存会浪费内存和时间。 所有线程的存储。需要一种惰性方法。这个不多 由于需要处理动态加载,因此增加了额外的负担 对象已经需要识别尚未存储的对象 已分配。这是停止所有线程和 在让所有线程再次运行之前为所有线程分配存储空间。