初始化 lambda 中不使用该成员的静态thread_local成员

Initialization of static thread_local member inside lambda that does not use that member

提问人:user2296145 提问时间:6/11/2023 最后编辑:user2296145 更新时间:6/12/2023 访问量:67

问:

我有类似于以下内容的代码(此代码不编译,仅用于说明目的):

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();
      }
 }
};

我们有一个 B 类型的static_thread本地成员local_,它内部有一个 lambda 队列,在调用 A::p ush 时推送。创建 B 时,它会将自身添加到 A 中的队列中。A::p oll 遍历此队列,并调用 B::work,后者运行之前推送的 lambda。我们可以从不同的线程调用 A::p ush 和 A::p oll。

我所看到的是,当从与最初调用 A::p ush 的线程不同的线程调用 A::p oll 时,此代码会死锁。原因是,对于调用 A::p oll 的线程,local_ 在执行推送到队列的 lambda 时正在初始化。就上下文而言,从未从调用 A::p oll 的线程调用 A::p ush。在这种情况下,lambda 的作用无关紧要,因为 lambda 对 local_ 不执行任何操作。

我在 cpp 规范中发现了一些可以解释正在发生的事情的东西:

“具有线程存储持续时间的变量应在 其首次使用ODR(6.2),如果建成,应在 线程退出。

我的问题是:为什么在运行 lambda 时会初始化local_?local_在执行 lambda 时是否被初始化,因为这是 local_ 的第一次 odr 使用(即使 local_ 真的没有在那里使用,我想 odr 使用中“使用”的定义可能不是人们直观地认为的那样)?

将其添加为 A::p oll 中的第一行:

(void)local_;

解决了该问题。

谢谢。

编辑:试图使问题更清晰:在此代码中:

void push() {
     local_.push([] () {
        // lambda that does things, none of them have anything to do with local_
     }
 }

编译器在 Lambda 中添加了对 for 的调用,问题是,如果 Lambda 内部未使用(甚至不可见)local_编译器为什么要这样做。__tls_initlocal_

C++ 多线程静态 初始化 单定义规则

评论

1赞 JaMiT 6/11/2023
“此代码不编译”——但您的实际代码确实编译(因为它执行)。这是一个问题,因为你可能已经改变了一些重要的细节,使问题无法回答。如果要演示代码,请演示真实代码。它可以/应该被简化为示例代码,但它仍然应该足够真实,以重现您所询问的情况。
0赞 Pepijn Kramer 6/11/2023
首先,您对线程本地成员的设计要求是什么?一开始就有一个感觉不对。
0赞 Solomon Slow 6/12/2023
只是一个吹毛求疵,但“lambda 队列”严格来说没有意义。你在那里得到的是一个功能对象的队列。严格来说,lambda 是源代码中的表达式。每次计算 lambda 时,它都会创建一个新的函数对象并将其放入队列中。将这些想法分开并不总是很重要,但有时,了解其中的区别会有所帮助。

答:

-2赞 Mahendra Panpalia 6/11/2023 #1

从伪代码来看,您似乎正在尝试复制线程池?如果是这样,为什么不使用条件变量实现线程池呢?

评论

0赞 JaMiT 6/12/2023
如果这是为了回答所述问题(“为什么在运行 lambda 时初始化local_?”),那么它似乎不合时宜。如果这是为了寻求对所述问题的澄清,那么它应该是一份评论。如果这是为了讨论 OP 正在做什么,它应该进入聊天状态。
0赞 Mahendra Panpalia 6/12/2023
我是新来的,所以还不能发表评论。它需要 50 个声望。
0赞 JaMiT 6/13/2023
老实说,这是违反礼仪的一个相当糟糕的借口。你基本上是在说,因为你不被允许发表评论,所以你违反了不同的规则,以便做你不被允许做的事情。两倍糟糕。在这种情况下,公认的做法是让别人处理它,至少在你获得必要的声誉之前。这可能意味着 OP 错过了您的帮助,但在更大的计划中,这比处理来自(其他)低代表用户的垃圾评论的损失要小。
0赞 Mahendra Panpalia 6/13/2023
对不起,伙计们。这不是借口,也不是违反礼仪。正如我所提到的,我是新手,将学习使用这个论坛的方式。此外,我很欣赏使用这个论坛必须遵守某些规则,但我仍在学习。希望你能理解。