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

78 Views Asked by At

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.

1

There are 1 best solutions below

4
Mahendra Panpalia On

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?