在 lambda 中通过引用捕获 thread_local 变量无法按预期工作
Capturing a thread_local variable by reference in lambda does not work as expected
我目前正在构建一个系统,其中我有多个线程 运行,一个线程可以将工作排队到另一个线程并等待完成。我正在使用互斥锁和 condition_variables 进行同步。为了避免为每个操作创建新的互斥锁和 cv,我想优化它并尝试为每个正在等待的线程使用 thread_local mutex/cv 对。然而,这出乎意料地不起作用,我很想知道为什么。
基本上我的队列代码在另一个线程中工作并等待它看起来像:
/* thread_local */ std::mutex mtx;
/* thread_local */ std::condition_variable cv;
bool done = false;
io_service.post([&]() {
// Execute the handler in context of the io thread
functionWhichNeedsToBeCalledInOtherThread();
// Signal completion to unblock the waiter
{
std::lock_guard<std::mutex> lock(mtx);
done = true;
}
cv.notify_one();
});
// Wait until queued work has been executed in io thread
{
std::unique_lock<std::mutex> lk(mtx);
while (!done) cv.wait(lk);
}
如果同步对象不是 thread_local
,这可以正常工作。当我添加 thread_local
时,等待线程永远等待,这表明条件变量永远不会发出信号。我现在有一种感觉,尽管通过引用捕获对象,但在 lambda 内部使用了另一个线程的 thread_local 对象。我什至可以通过检查 lambda 内部和外部的 mtx
地址来确认捕获没有做正确的事情 -> 它们不匹配。
问题是:
- 这是编译器中的错误还是设计使然?我正在使用 Visual Studio 2015,还没有与其他编译器核对过。
- 是否允许通过引用捕获
thread_local
变量?
我可以通过在 lambda 外部创建对 thread_local
变量的显式引用并在其中使用这些引用来解决该错误。但是我认为这种行为是出乎意料的,并且很想听听解释这是否是正确的行为。
要让 mutex 工作,每个需要同步的线程都必须锁定 same mutex。 thread_local
所做的是为每个线程创建一个不同的 mutex。如果您的每个线程都有自己独立的 mutex,它们就不可能通过它们进行通信。您需要 one mutex 让所有线程共享。
条件变量也是如此。所有线程都需要'talking'到同一个条件变量。这意味着为每个线程设置单独的 条件变量 没有意义。
关于您的 lambda,实例化 lambda 的每个线程都将捕获它自己的 thread_local
变量副本。鉴于您从 lambda 访问的 mutex 和 condition variable 是从其他线程访问的,因此没有同步,因为您的 lambda 正在使用完全不同的一组变量。
您观察到的是正确的行为,因为您实际上并没有捕捉到任何东西。静态和线程存储持续时间对象可以直接访问,因此为了提高效率 [&]
-capture 对它们没有影响。但是,您可以显式捕获适当的线程本地实例:
io_service.post([&mtx = mtx, &cv = cv]() {
我目前正在构建一个系统,其中我有多个线程 运行,一个线程可以将工作排队到另一个线程并等待完成。我正在使用互斥锁和 condition_variables 进行同步。为了避免为每个操作创建新的互斥锁和 cv,我想优化它并尝试为每个正在等待的线程使用 thread_local mutex/cv 对。然而,这出乎意料地不起作用,我很想知道为什么。
基本上我的队列代码在另一个线程中工作并等待它看起来像:
/* thread_local */ std::mutex mtx;
/* thread_local */ std::condition_variable cv;
bool done = false;
io_service.post([&]() {
// Execute the handler in context of the io thread
functionWhichNeedsToBeCalledInOtherThread();
// Signal completion to unblock the waiter
{
std::lock_guard<std::mutex> lock(mtx);
done = true;
}
cv.notify_one();
});
// Wait until queued work has been executed in io thread
{
std::unique_lock<std::mutex> lk(mtx);
while (!done) cv.wait(lk);
}
如果同步对象不是 thread_local
,这可以正常工作。当我添加 thread_local
时,等待线程永远等待,这表明条件变量永远不会发出信号。我现在有一种感觉,尽管通过引用捕获对象,但在 lambda 内部使用了另一个线程的 thread_local 对象。我什至可以通过检查 lambda 内部和外部的 mtx
地址来确认捕获没有做正确的事情 -> 它们不匹配。
问题是:
- 这是编译器中的错误还是设计使然?我正在使用 Visual Studio 2015,还没有与其他编译器核对过。
- 是否允许通过引用捕获
thread_local
变量?
我可以通过在 lambda 外部创建对 thread_local
变量的显式引用并在其中使用这些引用来解决该错误。但是我认为这种行为是出乎意料的,并且很想听听解释这是否是正确的行为。
要让 mutex 工作,每个需要同步的线程都必须锁定 same mutex。 thread_local
所做的是为每个线程创建一个不同的 mutex。如果您的每个线程都有自己独立的 mutex,它们就不可能通过它们进行通信。您需要 one mutex 让所有线程共享。
条件变量也是如此。所有线程都需要'talking'到同一个条件变量。这意味着为每个线程设置单独的 条件变量 没有意义。
关于您的 lambda,实例化 lambda 的每个线程都将捕获它自己的 thread_local
变量副本。鉴于您从 lambda 访问的 mutex 和 condition variable 是从其他线程访问的,因此没有同步,因为您的 lambda 正在使用完全不同的一组变量。
您观察到的是正确的行为,因为您实际上并没有捕捉到任何东西。静态和线程存储持续时间对象可以直接访问,因此为了提高效率 [&]
-capture 对它们没有影响。但是,您可以显式捕获适当的线程本地实例:
io_service.post([&mtx = mtx, &cv = cv]() {