为何未使用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
相关产品推荐
相关产品推荐

