C++11 thread_local 析构函数行为
C++11 thread_local destructor behaviour
我有以下情况:在 header "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;
}
上面的程序是否运行良好或正在访问 objectB,它具有来自 ObjectA 析构函数的 thread_local 存储空间,该析构函数还具有 thread_local 存储未定义行为?
如果是这样,为什么它会损坏,我该如何修复它?
[编辑,@Soonts 回答]
在实际用例中,A class 是模板,一个相当复杂的模板,而 B class 只是很大。 A objects 使用 shared_ptr<> 保存对 B 的引用,并且 B 的 thread_local 被访问 as-needed 基础。 (A 在主线程中构造并传递给工作线程)ObjectB::get_B() 因此在 ObjectA::get_A() 被调用之前可能不会被工作线程调用。
规范说明了一些关于生命周期的事情:
线程存储时长。对象的存储在线程开始时分配,在线程结束时释放。每个线程都有自己的对象实例。
如果具有线程存储持续时间的对象的构造函数或动态初始化的完成先于另一个,则第二个的析构函数的完成先于启动第一个析构函数。
现在回到您的代码。
你构造A,在构造函数中构造B。因此,B构造函数的完成发生在A构造函数完成之前。根据上面,当线程即将退出时,它会先销毁A,然后销毁B。根据规范的字母,你的代码是可以的。
实际上,我不确定 C++ 编译器是否将规范实现到那种程度的细节。如果我正在编写该代码,我就不会以这种方式使用 thread_local 对象。相反,我会把 B 放在 A 的非静态字段中。它比依赖语言标准的这种细微差别更简单,IMO 更可靠。
我有以下情况:在 header "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;
}
上面的程序是否运行良好或正在访问 objectB,它具有来自 ObjectA 析构函数的 thread_local 存储空间,该析构函数还具有 thread_local 存储未定义行为? 如果是这样,为什么它会损坏,我该如何修复它?
[编辑,@Soonts 回答]
在实际用例中,A class 是模板,一个相当复杂的模板,而 B class 只是很大。 A objects 使用 shared_ptr<> 保存对 B 的引用,并且 B 的 thread_local 被访问 as-needed 基础。 (A 在主线程中构造并传递给工作线程)ObjectB::get_B() 因此在 ObjectA::get_A() 被调用之前可能不会被工作线程调用。
规范说明了一些关于生命周期的事情:
线程存储时长。对象的存储在线程开始时分配,在线程结束时释放。每个线程都有自己的对象实例。
如果具有线程存储持续时间的对象的构造函数或动态初始化的完成先于另一个,则第二个的析构函数的完成先于启动第一个析构函数。
现在回到您的代码。
你构造A,在构造函数中构造B。因此,B构造函数的完成发生在A构造函数完成之前。根据上面,当线程即将退出时,它会先销毁A,然后销毁B。根据规范的字母,你的代码是可以的。
实际上,我不确定 C++ 编译器是否将规范实现到那种程度的细节。如果我正在编写该代码,我就不会以这种方式使用 thread_local 对象。相反,我会把 B 放在 A 的非静态字段中。它比依赖语言标准的这种细微差别更简单,IMO 更可靠。