通过lambda中的引用捕获thread_local变量不能按预期工作

时间:2017-07-18 19:06:09

标签: c++ multithreading lambda capture thread-local

我正在构建一个运行多个线程的系统,一个线程可以将工作排队到另一个线程并等待完成。我正在使用互斥锁和condition_variables进行同步。为了避免为每个操作创建一个新的互斥锁和cv,我想优化它并尝试为每个等待的线程使用thread_local互斥锁/ 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的地址来确认捕获没有做正确的事情 - &gt;他们不匹配。

问题是:

  • 这是编译器或设计中的错误吗?我正在使用Visual Studio 2015,尚未与其他编译器核对过。
  • 是否允许通过引用捕获thread_local变量?

我可以通过创建对lambda之外的thread_local变量的显式引用并在其中使用这些引用来解决错误。但是我认为这种行为是出乎意料的,并且很想听听这是否是正确行为的解释。

2 个答案:

答案 0 :(得分:3)

要使 mutex 工作,需要同步的每个线程都必须锁定相同的互斥锁。 thread_local的作用是为每个线程创建不同的互斥。如果你的每个线程都有自己独立的互斥,那么它们就无法通过它们进行通信。您需要一个 互斥锁来共享所有线程。

条件变量也是如此。所有线程都需要与同一个条件变量“对话”。这意味着为每个线程都有一个单独的条件变量是没有意义的。

关于你的lambda,实例化 lambda的每个线程都将捕获它自己的thread_local变量副本。鉴于您从lambda访问的互斥条件变量是从另一个线程访问的,因此没有同步,因为您的lambda使用完全不同的变量集。

答案 1 :(得分:1)

您观察到的是正确的行为,因为您实际上并没有捕获任何东西。静态和线程存储持续时间对象可以直接访问,因此为了提高效率[&] - 捕获对这些对象没有影响。但是,您可以显式捕获适当的线程本地实例:

io_service.post([&mtx = mtx, &cv = cv]() {