0

I have code that is similar to the following (this code does not compile, is just for illustration purposes):

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

We have a static_thread local member local_ of type B, which internally has a queue of lambdas, which are pushed when A::push is called. When B is created it adds itself to a queue in A. A::poll goes through this queue, and calls B::work, which runs the lambdas that were previously pushed. We can call A::push and A::poll from different threads.

What I am seeing is that this code deadlocks when calling A::poll from a different thread than the thread that called A::push originally. The reason is that local_, for the thread that is calling A::poll, is being initialized when the lambda that was push into the queue is executed. For context, A::push was never called from the thread that is calling A::poll. What the lambda does is not relevant in this case, as the lambda does nothing with local_.

I found something in the cpp specs that might explain what is happening:

"A variable with thread storage duration shall be initialized before its first odr-use (6.2) and, if constructed, shall be destroyed on thread exit."

My question is: why is local_ being initialized when running the lambda? Is local_ being initialized when executing the lambda because that is the first odr-use of local_ (even though local_ is really not being used there, I guess the definition of "use" in odr-use might not be what one would intuitively think)?

Adding this as the first line in A::poll:

(void)local_;

fixes the issue.

Thanks.

EDIT: trying to make the question clearer: in this code:

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

the compiler is adding a call to __tls_init for local_ inside the lambda, and the question is why is the compiler doing that if local_ is not used (or even visible) inside the lambda.

user2296145
  • 276
  • 2
  • 8
  • 1
    *"this code does not compile"* -- and yet your actual code does compile (since it executes). This is a problem, as you've probably changed some significant details, making the question unanswerable. If you're going to demonstrate code, demonstrate real code. It can/should be simplified, sample code, but it should still be real enough to **reproduce** the situation you are asking about. – JaMiT Jun 11 '23 at 07:37
  • What is your design requirement for having a thread local member in the first place? It just doesn't feel right to have one in the first place. – Pepijn Kramer Jun 11 '23 at 07:54
  • Just a nit-pick, but "queue of lambdas" doesn't strictly make sense. What you've got there is a queue of _functional objects._ Strictly speaking, a lambda is an _expression_ in your source code. Each time the lambda is _evaluated,_ it creates a new functional object that you put into your queue. It's not always important to keep those ideas separate, but sometimes, it helps to know the difference. – Solomon Slow Jun 11 '23 at 17:49

1 Answers1

-2

From the pseudo code it seems you are trying to replicate a thread pool? If so why don't you implement a thread pool using condition variable?

  • If this was intended as an answer to the stated question (*"why is local_ being initialized when running the lambda?"*), it appears to be off the mark. If this was intended to seek clarification of the stated question, it should have been a comment. If this was intended to discuss what the OP is working on, it should have gone into chat. – JaMiT Jun 11 '23 at 18:46
  • I am new so not able to comment yet. It needs 50 reputations. – Mahendra Panpalia Jun 12 '23 at 15:11
  • Honestly, that's a rather poor excuse for breaking decorum. You are basically saying that because you are not allowed to comment, you broke a different rule in order to do what you're not allowed to do. Twice as bad. The accepted course of action in this situation is to let someone else handle it, at least until you collect the necessary reputation. It might mean that the OP misses out on your help, but in the larger scheme of things, that's a smaller loss than dealing with spam comments from (other) low-rep users. – JaMiT Jun 13 '23 at 06:09
  • Apologies guys. It is no excuse and no decorum breaking. As I have mentioned I am new and will learn the way of using this forum. Also I appreciate that certain rules have to be followed to use this forum but I am still learning. Hope you understand. – Mahendra Panpalia Jun 13 '23 at 09:03