C++中的易失性11

Let*_*_Be 27 c++ volatile c++11

在C++ 11标准中,机器模型从单线程机器变为多线程机器.

这是否意味着static int x; void func() { x = 0; while (x == 0) {} }优化输出读取的典型示例将不再发生在C++ 11中?

编辑:对于那些不知道这个例子的人(我非常惊讶),请阅读:https://en.wikipedia.org/wiki/Volatile_variable

EDIT2:好的,我真的很期待所有知道volatile这个例子的人.

如果您使用示例中的代码,则循环中读取的变量将被优化,使循环无限.

解决方案当然是使用volatile它会强制编译器在每次访问时读取变量.

我的问题是,如果这是C++ 11中不推荐使用的问题,因为机器模型是多线程的,因此编译器应该考虑对系统中存在的变量的并发访问.

Nic*_*las 78

是否优化完全取决于编译器以及他们选择优化的内容.C++ 98/03内存模型无法识别可能x在其设置和值的检索之间发生变化的可能性.

C++ 11内存模型确实可以识别x可以更改的内存.但是,它并不关心.对变量的非原子访问(即:不使用std::atomics或适当的互斥锁)会产生未定义的行为.因此,对于C++ 11编译器来说,假设x从不在写入和读取之间进行更改是完全正确的,因为未定义的行为可能意味着"函数永远不会x发生变化".

现在,让我们来看看C++ 11所说的内容volatile int x;.如果你把它放在那里,并且你有其他一些线程x,你仍然有未定义的行为.易失性不会影响线程行为.C++ 11的内存模型不定义从/到原始的读取或写入x,也不需要非原子读/写所需的内存屏障才能正确排序.volatile与这种或那种方式无关.

哦,你的代码可能会起作用.但是C++ 11并不能保证它.

什么volatile告诉编译器的是,它不能优化内存从该变量读取.但是,CPU内核具有不同的缓存,并且大多数内存写入不会立即发送到主内存.它们存储在该核心的本地缓存中,并且可能最终被写入.

CPU有办法强制缓存线进入内存并在不同内核之间同步内存访问.这些内存屏障允许两个线程有​​效地进行通信.仅仅从一个用另一个核心编写的核心中读取内存是不够的; 编写内存的核心需要发布一个障碍,读取它的核心需要在读取它之前完成该障碍才能实际获取数据.

volatile保证不这一切.易失性与"硬件,映射内存和内容"一起使用,因为写入该内存的硬件可确保缓存问题得到解决.如果CPU核心在每次写入后都发出了内存屏障,那么基本上可以吻别任何表现再见的希望.所以C++ 11有特定的语言,说明何时需要构造来发布障碍.

volatile是关于内存访问(何时读取); 线程是关于内存完整性(实际存储在那里).

C++ 11内存模型具体说明哪些操作会导致一个线程中的写入在另一个线程中变得可见.它是关于内存完整性的,这不是volatile处理的东西.而内存完整性通常要求两个线程都做一些事情.

例如,如果线程A锁定互斥锁,执行写入操作,然后将其解锁,则C++ 11内存模型只需要在线程B稍后将其写入时对线程B可见.直到它实际获得该特定锁定,它未定义具有什么价值.这个东西在标准1.10节中详细列出.

让我们看看你引用的代码,就标准而言.第1.10节,第8节谈到了某些库调用使线程与另一个线程"同步"的能力.大多数其他段落解释了同步(和其他东西)如何在线程之间构建操作顺序.当然,您的代码不会调用任何此类代码.没有同步点,没有依赖顺序,没有.

没有这种保护,没有某种形式的同步或排序,1.10 p21进来:

程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且都不会在另一个之前发生.任何此类数据争用都会导致未定义的行为.

您的程序包含两个相互冲突的操作(读取x和写入x).两者都不是原子的,也不是通过同步在另一个之前发生的.

因此,您已经实现了未定义的行为.

因此,如果您使用正确的互斥锁或正确的原子加载/存储调用,那么C++ 11内存模型可以保证多线程行为的唯一情况就是如此std::atomic<int> x.

哦,你也不需要制造x易变的东西.无论何时调用(非内联)函数,该函数或其调用的函数都可以修改全局变量.因此,它不能优化掉的读xwhile循环.每个要同步的C++ 11机制都需要调用一个函数.这恰好会调用内存屏障.

  • 啊,最后一个提到可见性问题的答案.当我告诉他们时,几乎没有人相信我"有些线程会给一些易变变量写一个新值.但是在C++中,`volatile`就不能保证任何其他线程都能看到更新的值." (8认同)
  • @Let_Me_Be:它确实依赖于排序和原子性.从另一个线程的变化必须变为*可见*.这意味着它必须在此线程中的某些操作之前被命令*.由于`volatile`不涉及排序,因此不能保证它会变得可见. (3认同)