You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何未使用static thread_local成员的lambda会触发其初始化?

问题分析:thread_local变量意外触发初始化导致死锁

示例代码

class A {
  std::mutex m_;
  std::vector<B*> bv_;

  struct B {
    B() { 
         std::lock_guard _(m_);
         bv_.push_back(this);
      }
    
      template<typename Lambda>
      void push(Lambda&& lambda) {
         // saves lambda in a queue
      }

     void work() {
      // executes the lambdas from the queue
     }       
   };

  static thread_local B local_;

public:
 void push() {
     local_.push([] () {
        // lambda that does things
     });
 }
 void poll() {
      std::lock_guard _(m_);
      for (auto * b : bv_) {
          b->work();
      }
 }
};

代码逻辑与问题现象

  • 类A包含static thread_local类型成员local_(B类型),B的构造函数会将自身加入A的bv_队列,且构造时会持有A的互斥量m_。
  • A::push会向当前线程的local_的lambda队列添加任务;A::poll会先持有m_,再遍历bv_队列调用B::work执行lambda任务。

实际运行中,当从未调用过A::push的线程执行A::poll时会发生死锁:该线程的local_会在lambda执行时被触发初始化,而lambda本身并未使用local_。根据C++标准,线程存储期变量需在首次ODR-use前初始化。

核心疑问

为何lambda执行会触发local_的初始化?即使lambda未使用甚至不可见local_,编译器仍在lambda内添加__tls_init调用的原因是什么?另外,在A::poll首行添加(void)local_;即可解决死锁,这背后的逻辑是什么?

原因解释

这是编译器对ODR-use判定的保守处理导致的:

  • lambda定义在A::push函数内,而A::push直接访问了static thread_local变量local_。编译器会做保守性假设,认为lambda可能存在对local_的间接依赖,因此在lambda执行入口插入了对local_的初始化检查(即__tls_init调用)。
  • 当未调用过A::push的线程执行A::poll时,poll先持有了m_,随后遍历队列执行lambda;此时lambda触发local_初始化,B的构造函数需要抢m_,但m_已经被poll持有,形成循环等待,最终死锁。

而在A::poll首行添加(void)local_;的作用是主动提前触发当前线程local_的初始化:

  • 这行代码会在poll加锁前触发local_的初始化,此时m_还未被持有,B的构造函数可以顺利获取锁并将自身加入队列。
  • 后续poll加锁遍历队列时,local_已经完成初始化,执行B::work时不会再触发初始化操作,也就避免了死锁。

内容的提问来源于stack exchange,提问作者user2296145

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 10:17:41