如何让编译器不执行__thread修饰的线程局部变量的析构函数
你观察到的__thread变量执行析构是符合GCC/Clang实现的预期行为:对于带有非平凡构造/析构函数的类型,编译器会自动为__thread变量生成构造调用,同时将析构函数注册到线程退出清理回调中,主线程退出时这些回调会被__run_exit_handlers执行,对应你栈追踪显示的调用链。
下面针对你的两个问题给出可落地的解决方案:
调整内存池生命周期,覆盖所有线程局部变量的生命周期
你可以通过控制初始化/销毁优先级,保证内存池最后销毁:
- 利用同编译单元全局对象的顺序规则:将内存池实例定义在同一编译单元的所有依赖该内存池的线程局部/全局变量之前。同编译单元内的全局对象构造顺序与定义顺序一致,析构顺序与定义顺序相反,即可保证内存池先初始化,所有线程局部变量销毁完成后才会销毁内存池。
- 用编译器属性指定初始化销毁优先级:给内存池的初始化函数添加
__attribute__((constructor(101)))属性,销毁函数添加__attribute__((destructor(101)))属性。优先级数值越小执行时机越早,100以内优先级为glibc内部保留,使用101以上的数值可以保证内存池初始化早于所有普通全局/线程局部变量,销毁晚于所有普通全局/线程局部变量。
如果是多线程场景下的非主线程线程局部变量,只需要将每个线程的内存池实例清理回调注册到更高优先级的线程局部清理链即可。
完全禁止该变量执行析构逻辑
有两种无额外运行时开销的实现方式:
- 直接给变量添加
no_destroy编译器属性。GCC和Clang均支持该属性,添加后编译器不会将该变量的析构函数注册到退出清理链,完全不会执行析构,也不会影响你正常使用变量:
struct Dummy { ~Dummy() { assert(0); } }; __thread __attribute__((no_destroy)) Dummy dumb;
- 用对齐的字符数组占位,完全由你控制对象生命周期,编译器不会自动生成构造/析构调用:
struct Dummy { ~Dummy() { assert(0); } }; __thread alignas(Dummy) char dumb_buf[sizeof(Dummy)]; // 需要访问Dummy实例时做类型转换即可 Dummy* get_dumb() { return reinterpret_cast<Dummy*>(dumb_buf); }
内容的提问来源于stack exchange,提问作者Eric Stotch
相关产品推荐
相关产品推荐

