user2269707
user2269707

Reputation:

Did my compiler ignore my unused static thread_local class member?

I want do some thread registration in my class, so I decide to add a check for the the thread_local feature:

#include <iostream>
#include <thread>

class Foo {
 public:
  Foo() {
    std::cout << "Foo()" << std::endl;
  }
  ~Foo() {
    std::cout << "~Foo()" << std::endl;
  }
};

class Bar {
 public:
  Bar() {
    std::cout << "Bar()" << std::endl;
    //foo;
  }
  ~Bar() {
    std::cout << "~Bar()" << std::endl;
  }
 private:
  static thread_local Foo foo;
};

thread_local Foo Bar::foo;

void worker() {
  {
    std::cout << "enter block" << std::endl;
    Bar bar1;
    Bar bar2;
    std::cout << "exit block" << std::endl;
  }
}

int main() {
  std::thread t1(worker);
  std::thread t2(worker);
  t1.join();
  t2.join();
  std::cout << "thread died" << std::endl;
}

The code is simple. My Bar class has a static thread_local member foo. If a static thread_local Foo foo is created, it means a thread is created.

But when I run the code, nothing in the Foo() prints, and if I remove the comment in Bar's constructor, which uses foo, the code works fine.

I tried this on GCC (7.4.0) and Clang (6.0.0) and the results are the same. I guess that the compiler discovered that foo is unused and do not generate an instance. So

  1. Did the compiler ignore the static thread_local member? How can I debug for this?
  2. If so, why does a normal static member not have this problem?

Upvotes: 10

Views: 384

Answers (2)

user2269707
user2269707

Reputation:

I found this information in "ELF Handling For Thread-Local Storage" which can prove @L.F. 's answer

In addition the run-time support should avoid creating the thread-local storage if it is not necessary. For instance, a loaded module might only be used by one thread of the many which make up the process. It would be a waste of memory and time to allocate the storage for all threads. A lazy method is wanted. This is not much extra burden since the requirement to handle dynamically loaded objects already requires recognizing storage which is not yet allocated. This is the only alternative to stopping all threads and allocating storage for all threads before letting them run again.

Upvotes: 1

L. F.
L. F.

Reputation: 20559

There is no problem with your observation. [basic.stc.static]/2 prohibits eliminating variables with static storage duration:

If a variable with static storage duration has initialization or a destructor with side effects, it shall not be eliminated even if it appears to be unused, except that a class object or its copy/move may be eliminated as specified in [class.copy].

This restriction is not present for other storage durations. In fact, [basic.stc.thread]/2 says:

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

This suggests that a variable with thread storage duration need not be constructed unless odr-used.


But why is this discrepancy?

For static storage duration, there is only one instance of a variable per program. The side effects of construction thereof can be significant (kinda like a program-wide constructor), so the side effects are required.

For thread local storage duration, however, there is a problem: an algorithm may start a lot of threads. For most of these threads, the variable is completely irrelevant. It would be hilarious if an external physics simulation library that calls std::reduce(std::execution::par_unseq, first, last) ends up creating a lot of foo instances, right?

Of course, there can be a legitimate use for side effects of the construction of variables of thread local storage duration that are not odr-used (e.g., a thread tracker). However, the advantage for guaranteeing this is not enough to compensate for the aforementioned drawback, so these variables are allowed to be eliminated as long as they aren't odr-used. (Your compiler can choose not to do, though. And you can also make your own wrapper around std::thread that takes care of this.)

Upvotes: 8

Related Questions