thread_local 变量的实例是否保证由访问它们的线程初始化?

sta*_*384 5 c++ multithreading language-lawyer c++17 c++20

“所有具有线程局部存储持续时间的非局部变量都作为线程启动的一部分进行初始化,在线程函数开始执行之前进行排序。” https://en.cppreference.com/w/cpp/language/initialization
但是, cppreference 似乎没有提到这些变量实际上是由正在启动的线程还是由正在启动的线程初始化新线程。
考虑以下示例:

(id.hpp)

#include<thread>
extern thread_local const std::thread::id this_thread_id;
Run Code Online (Sandbox Code Playgroud)

(id.cpp)

#include "id.hpp"
thread_local const std::thread::id this_thread_id = std::this_thread::get_id();
Run Code Online (Sandbox Code Playgroud)

如果我this_thread_id从一个不是主线程的线程访问,我会得到那个线程的 id 吗?如果this_thread_id被初始化为“作为线程启动的一部分”,它也可能由原始调用线程初始化,因此this_thread_id将具有不正确的值。

我知道有一些设计模式可以完全避免这个问题,但这违背了这个问题的全部意义,所以我不想在这里使用它们。

Man*_*uel 1

作为本地线程,每个线程都会有一个副本,因此每个线程中都会有不同的值。

至于什么时候初始化:

在线程初始函数的第一个语句之前排序或被推迟。如果延迟,则与线程 t 的实体关联的初始化将在 t 对任何非内联变量的第一次非初始化 odr-use 之前进行排序,该非内联变量的线程存储持续时间与要初始化的变量在同一转换单元中定义

因此,它在每个线程中初始化,无论是在运行线程函数之前还是在使用之前。

我编写了一个测试:

#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;
}
Run Code Online (Sandbox Code Playgroud)

这是输出:

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
Run Code Online (Sandbox Code Playgroud)

所以看起来它g++是在第一次使用时初始化的。即使您只访问它所在的内存。