std::condition_variable 的布尔谓词在 C++ 中应该是易失性的吗?

Ron*_*nen 4 c++ multithreading std undefined-behavior c++11

我听到了很多相互矛盾的答案,现在我不知道该怎么想。公认的知识是,为了在 C++ 中以线程安全的方式共享内存,需要将 volatilestd::mutex 一起使用。

基于这种理解,我一直在编写这样的代码:

volatile bool ready = false;
std::condition_variable cv;
std::mutex mtx;
std::unique_lock<std::mutex> lckr{ mtx };
cv.wait(lckr, [&ready]() -> bool { return ready; });
Run Code Online (Sandbox Code Playgroud)

但后来我在 CppCon 上看到了 Chandler Carruth 的演讲,他说(作为旁注)在这种情况下不需要 volatile,而且我基本上不应该使用 volatile。

然后我在 Stack Overflow 上看到其他答案说永远不应该使用 volatile,而且它还不够好,而且根本不能保证原子性。

钱德勒·卡鲁斯正确吗?我们都错了吗?

现在我有3个选择:

  1. 必须使用 volatile 或 std::atomic
  2. 任何布尔值都可以
  3. 必须是 std::atomic

我想知道 C++14 ISO 标准是否允许我编写如下代码:

#include <condition_variable>
#include <mutex>
#include <iostream>
#include <future>
#include <functional>

struct sync_t
{
    std::condition_variable cv;
    std::mutex mtx;
    bool ready{ false };
};
static void threaded_func(sync_t& sync)
{
    std::lock_guard<std::mutex> lckr{ sync.mtx };
    sync.ready = true;
    std::cout << "Waking up main thread" << std::endl;
    sync.cv.notify_one();
}
int main()
{
    sync_t sync;
    {
        std::unique_lock<std::mutex> lckr{ sync.mtx };
        sync.ready = false;
        std::future<void> thread =
            std::async(std::launch::async, threaded_func, std::ref(sync));
        std::cout << "Preparing to sleep" << std::endl;
        sync.cv.wait(lckr, [&sync]() -> bool { return sync.ready; });
        thread.get();
    }
    std::cout << "Done program execution" << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当我成功时会发生什么:

volatile bool ready{ false };
Run Code Online (Sandbox Code Playgroud)

当我成功时会发生什么:

std::atomic<bool> ready{ false };
Run Code Online (Sandbox Code Playgroud)

Adr*_*ica 5

限定符对从不同线程 \xe2\x80\x93 访问对象volatile没有必需的影响,它仅保证编译器不会优化单个线程中修改的副作用。来自 cppreference(粗体强调我的):

\n
\n
    \n
  • 易失性对象- 类型为易失性限定的对象,或者易失性对象的子对象,或者 const-易失性对象的可变子对象。出于优化目的(即,在\n执行时,易失性访问无法被优化或重新排序,\n这会产生另一个可见的副作用,即在易失性访问之前或\n之后排序。这使得易失性对象\n适合与信号处理程序通信,但不适用于\n其他线程执行,请参阅 std::memory_order)。任何通过非易失性类型的泛左值引用易失性对象的尝试(例如,通过对非易失性类型的引用或指针)都会导致未定义的行为。
  • \n
\n
\n

为了防止从多个线程访问对象时发生未定义的行为,您应该使用std::atomic对象。再次,来自 cppreference

\n
\n

std::atomic 模板的每个实例化和完全特化都定义了一个原子类型。如果一个线程写入原子对象,而另一个线程从中读取,则行为是明确定义的(有关数据争用的详细信息,请参阅内存模型)。

\n

此外,对原子对象的访问可以建立线程间同步并按照 std::memory_order 指定的顺序对非原子内存访问进行排序。

\n
\n

  • @pptaszni 可能不会,只要所有访问都以“lock_guard”开头即可。但OP也提出了一个更普遍的问题,即“易失性”的作用。 (2认同)