在 lambda 中捕获 `thread_local`

Mik*_*ine 16 c++ language-lawyer thread-local-storage c++11 stdthread

在 lambda 中捕获 thread_local:

#include <iostream>
#include <thread>
#include <string>

struct Person
{
    std::string name;
};

int main()
{
    thread_local Person user{"mike"};
    Person& referenceToUser = user;

    // Works fine - Prints "Hello mike"
    std::thread([&]() {std::cout << "Hello " << referenceToUser.name << std::endl;}).join();

    // Doesn't work - Prints "Hello"
    std::thread([&]() {std::cout << "Hello " << user.name << std::endl;}).join();

    // Works fine - Prints "Hello mike"
    std::thread([&user=user]() {std::cout << "Hello " << user.name << std::endl;}).join();
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/zeocG5ohb

看起来如果我使用 a 的原始名称,thread_local那么执行 lambda 的线程上的值就是thread_local运行 lambda 的线程的版本。但是,一旦我获取对本地线程的引用或指针,它就会变成(指向)原始线程实例的指针。

这里有什么规则。我可以依赖这个分析吗?

Jan*_*tke 16

与本地对象类似static,本地thread_local(隐式static thread_local)对象在控制第一次通过其声明时被初始化。

您创建的线程永远不会执行main,只有主线程执行,因此您user在额外线程的生命周期开始之前进行访问。

你的三个案例的解释

std::thread([&]() {std::cout << "Hello " << referenceToUser.name << std::endl;}).join();
Run Code Online (Sandbox Code Playgroud)

我们正在捕获referenceToUser它指的是user主线程上的。这没关系。

std::thread([&]() {std::cout << "Hello " << user.name << std::endl;}).join();
Run Code Online (Sandbox Code Playgroud)

我们正在user额外线程的生命周期开始之前对其进行访问。这是未定义的行为。

std::thread([&user=user]() {std::cout << "Hello " << user.name << std::endl;}).join();
Run Code Online (Sandbox Code Playgroud)

我们再次user从主线程引用 ,这与第一种情况相同。

可能的修复

如果您user在 之外声明main,则将user在线程启动时初始化,而不是在main运行时初始化:

thread_local Person user{"mike"};

int main() {
    // ...
Run Code Online (Sandbox Code Playgroud)

或者,user在 lambda 表达式内部声明。


注意:没有必要捕获thread_local对象。第二个例子可以是[] { ... }