【问题标题】:C++11 thread_local destructor behaviourC++11 thread_local 析构函数行为
【发布时间】:2019-01-27 22:58:55
【问题描述】:

我有以下情况:在标题“test.hpp”中我定义:

class ObjectA {
    public:
        ObjectA();
        ~ObjectA();
        static ObjectA & get_A();
};
class ObjectB {
    public:
        ~ObjectB();
        static ObjectB & get_B();
        void do_cleanup();
};

我在单独的编译单元中实现了 ObjectB:

#include "test.hpp"
#include <iostream>
ObjectB::~ObjectB() {
    std::cout<<"ObjectB dtor"<<std::endl;
}
ObjectB & ObjectB::get_B() {
    thread_local ObjectB b_instance;
    return b_instance;
}
void ObjectB::do_cleanup() {
    std::cout<<"Clearing up B garbage..."<<std::endl;
}

对象A:

#include "test.hpp"
#include <iostream>
ObjectA::ObjectA() {
    ObjectB::get_B(); <--dummy call to initialize thread_local ObjectB;
}
ObjectA::~ObjectA() {
     std::cout<<"ObjectA dtor"<<std::endl;
     ObjectB::get_B().do_cleanup(); // <-- is this undefined behaviour??
}
ObjectA & ObjectA::get_A() {
     thread_local ObjectA a_instance;
     return a_instance;
}

最后是一个测试 main():

#include <thread>
#include "test.hpp"
int main() {
    std::thread check([](){
    ObjectA::get_A(); //<--dummy call just to initialize thread_local object.
    });
    check.join();
    return 0;
}

上述程序是否表现良好或正在访问具有来自 ObjectA 析构函数的 thread_local 存储的 objectB,该析构函数也具有 thread_local 存储未定义的行为? 如果是这样,为什么会损坏?我该如何解决?

most related question I found

[编辑,@Sonts 回答]

在实际用例中,A 类是模板,一个相当复杂的模板,而 B 类只是很大。 A 对象使用 shared_ptr 持有对 B 的引用,并且 B 的 thread_locals 根据需要进行访问。 (A 在主线程中构造并传递给工作线程)因此在 ObjectA::get_A() 被调用之前,工作线程可能不会调用 ObjectB::get_B()。

【问题讨论】:

  • 您是否检查过您的编译器是否支持线程局部变量?并非所有编译器都赶上了标准的这一部分。但是顺序看起来是正确的。您在 A 的构造函数中使用 B 确保它在 A 之前存在。所以 A 将首先被破坏。调用 B 清理然后 B 将被销毁。这一切都应该发生在同一个线程的范围内。
  • 注:使用LLVM version 8.0.0在mac上编译运行正确

标签: c++ destructor thread-local


【解决方案1】:

规范说明了关于生命周期的几件事:

Storage class specifiers

线程存储持续时间。对象的存储在线程开始时分配,在线程结束时释放。每个线程都有自己的对象实例。

Termination

如果构造函数的完成或具有线程存储持续时间的对象的动态初始化在另一个之前排序,则第二个析构函数的完成将在第二个析构函数的启动之前排序第一个。

现在回到你的代码。

你构造A,在构造函数中构造B。因此,B构造函数的完成发生在A构造函数的完成之前。根据上述,当线程即将退出时,它会先销毁A然后B。根据规范的字母,您的代码是可以的。

实际上,我不确定 C++ 编译器将规范实现到那种详细程度。如果我正在编写该代码,我不会以这种方式使用 thread_local 对象。相反,我会将 B 放在 A 的非静态字段中。它比依赖语言标准的这种细微差别更简单,IMO 更可靠。

【讨论】:

  • 好的,谢谢您的澄清。我强硬我打了thread_local版本的静态对象破坏地狱。 (未定义两个编译单元的静态对象被销毁的顺序)
猜你喜欢
  • 1970-01-01
  • 2016-03-18
  • 2016-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-10
  • 1970-01-01
相关资源
最近更新 更多