Are instances of thread_local variables guaranteed to be initialised by the thread that accesses them?

Viewed 155

"All non-local variables with thread-local storage duration are initialized as part of thread launch, sequenced-before the execution of the thread function begins." (https://en.cppreference.com/w/cpp/language/initialization)
However, cppreference does not seem to mention whether or not those variables are actually initialised by the thread that is being launched, or by the thread that is launching the new thread.
Consider the following example:

(id.hpp)

#include<thread>
extern thread_local const std::thread::id this_thread_id;

(id.cpp)

#include "id.hpp"
thread_local const std::thread::id this_thread_id = std::this_thread::get_id();

If I access this_thread_id from a thread that is not the main thread, am I going to get the id of that thread? If this_thread_id is initialised "as part of the thread launch", it might as well be initialised by the original calling thread, and as such this_thread_id would have an incorrect value.

I understand that there are design patterns to avoid this problem altogether, but that defeats the whole point of this question, so I don't want to use them here.

1 Answers

Being a thread local, there will be a copy for each thread, so you'll have a different value in each thread.

As for when it is initialized:

is sequenced before the first statement of the initial function of a thread or is deferred. If it is deferred, the initialization associated with the entity for thread t is sequenced before the first non-initialization odr-use by t of any non-inline variable with thread storage duration defined in the same translation unit as the variable to be initialized

So it is initialized in each thread, be it before running the thread function or before it is used.

I've compiled a test:

#include <thread>
#include <iostream>

namespace m {
    std::thread::id get_id () {
        std::cout << "My get id called." << std::endl;
        return std::this_thread::get_id();
    }
}

thread_local const std::thread::id this_thread_id = m::get_id();

int main ()
{
    std::cout << "Main: " << this_thread_id << std::endl;
    
    std::thread th2 (
        [&]{
            uint64_t i {0};
            std::cout << "Thread.int: " << i << std::endl;
            std::cout << "Here it could be uninitialized." << std::endl;
            std::cout << "Thread .2: " << this_thread_id << std::endl;
        });

    th2.join ();
    
    std::cout << "End" << std::endl;
    
    return 0;
}

And this is the output:

manuel@desktop:~/projects/soanswers/src (master)$ g++ ini.cc -o ini -std=c++2a -pthread && ./ini
Main: My get id called.
140064769697600
Thread.int: 0
Here it could be uninitialized.
Thread .2: My get id called.
140064769693440
End

So it seems in g++ it is initialized at first use. Even if you access only the memory it is on.

Related