在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();
}
https://godbolt.org/z/zeocG5ohb
看起来如果我使用thread_local
的原始名称,那么它在执行lambda的线程上的值就是运行lambda的线程的thread_local
版本。但是,一旦我引用或指针到线程本地,它就会变成(指向)* 原始 * 线程示例。
这里的规矩是什么。我能相信这个分析吗?
2条答案
按热度按时间oprakyz71#
与局部
static
对象类似,局部thread_local
(隐式static thread_local
)对象在控制权首次通过其声明时初始化。您正在创建的线程永远不会执行
main
,只有主线程执行,因此您在user
的生命周期在额外线程上开始之前访问它。你的三个案例解释
我们正在捕获
referenceToUser
,它引用了主线程上的user
。没事的我们在额外线程的生存期开始之前访问它的
user
。这是未定义的行为。再次,我们在这里引用主线程的
user
,这与第一种情况相同。可能的修复
如果你在
main
之外声明user
,那么它将在你的线程初始化时被初始化,而不是在main
运行时被初始化:或者,在lambda表达式中声明
user
。thread_local
对象。第二个例子可以是[] { ... }
。czfnxgou2#
[&]
是一个非常危险的事情,当你的lambda在直接上下文之外执行时。在那里非常有用--否则,这是一个糟糕的计划。在这种情况下,你会被
[&]
* 不 * 捕获全局、局部静态或线程局部变量这一事实所困扰。所以在这种情况下使用[]
的行为是一样的。这通过引用捕获
referenceToUser
。referenceToUser
反过来引用主线程中的thread_local
变量。这与
在这里使用
[&]
会让您相信它是通过引用捕获user
的。因此使用了thread_local
变量main::user
。由于线程从未传递该变量的初始化行,所以您刚刚完成了UB。在这里,您在lambda创建时显式创建了一个新引用变量
user
。基本规则是**在创建lambda传递给
std::thread
时永远不要使用[&]
。这是
[&]
的适当用法:lambda预期存在于当前范围内,并且将在本地执行。
[&]
安全。这是
[&]
有效使用的另一个例子,因为这个pop
操作被重构到一个helper中,并且可能在本地函数中运行不止一次。但是如果lambda不是在本地运行的,或者可能超出当前的作用域,那么
[&]
就是一个有毒的选项,在我见过的几乎每一个使用它的情况下都会导致意外和错误。