是否优化完全取决于编译器以及他们选择优化的内容。 C++98/03 内存模型无法识别x 可能在它的设置和值的检索之间发生变化。
C++11 内存模型确实承认x 可以更改。然而,它不在乎。对变量的非原子访问(即:不使用std::atomics 或适当的互斥锁)会产生未定义的行为。因此,对于 C++11 编译器来说,假设 x 在写入和读取之间永远不会发生变化是完全可以的,因为未定义的行为可能意味着“函数永远不会看到 x 发生变化。”
现在,让我们看看 C++11 对volatile int x; 的看法。如果你把它放在那里,并且你有一些其他线程与x 混淆,你仍然有未定义的行为。 Volatile 不会影响 threading 行为。 C++11 的内存模型没有将来自/到x 的读取或写入定义为原子的,也不需要正确排序非原子读取/写入所需的内存屏障。 volatile 与此无关。
哦,您的代码可能有效。但是 C++11 并没有保证。
volatile 告诉编译器的是它无法优化从该变量读取的内存。但是,CPU 内核具有不同的缓存,并且大多数内存写入不会立即发送到主内存。它们被存储在该内核的本地缓存中,并且可能被写入...最终。
CPU 有办法将高速缓存线强制输出到内存中,并在不同内核之间同步内存访问。这些内存屏障允许两个线程有效地通信。仅仅从一个内核中读取另一个内核中写入的内存是不够的。写入内存的核心需要发出一个屏障,而正在读取它的核心需要在读取它之前完成该屏障才能真正获取数据。
volatile 保证这一切都没有。 Volatile 与“硬件、映射内存和其他东西”一起使用,因为写入该内存的硬件确保缓存问题得到解决。如果 CPU 内核在每次写入后都会发出内存屏障,那么您基本上可以告别任何性能希望。因此,C++11 有特定的语言来说明何时需要构造来发出障碍。
volatile 是关于内存访问(何时读取);线程是关于内存完整性(实际存储在那里的内容)。
C++11 内存模型具体说明了哪些操作会导致一个线程中的写入在另一个线程中变得可见。这是关于内存完整性,这不是volatile 处理的事情。而且内存完整性通常需要两个线程都做某事。
例如,如果线程 A 锁定一个互斥体,执行写入,然后解锁它,则 C++11 内存模型仅要求线程 B 稍后锁定它时,该写入对线程 B 可见。在它真正获得那个特定的锁之前,它是什么值是未定义的。这些东西在标准的第 1.10 节中有详细的说明。
让我们看看the code you cite,关于标准。第 1.10 节,p8 谈到了某些库调用使线程与另一个线程“同步”的能力。大多数其他段落解释了同步(和其他事情)如何在线程之间建立操作顺序。当然,您的代码不会调用任何此类。没有同步点,没有依赖排序,什么都没有。
如果没有这种保护,没有某种形式的同步或排序,1.10 p21 就会出现:
如果一个程序在不同的线程中包含两个相互冲突的动作,则该程序的执行包含一个数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生。任何此类数据竞争都会导致未定义的行为。
您的程序包含两个相互冲突的操作(读取 x 和写入 x)。两者都不是原子的,也不是通过同步排序在另一个之前发生。
因此,您实现了未定义的行为。
因此,您获得 C++11 内存模型保证多线程行为的唯一情况是,如果您使用适当的互斥锁或 std::atomic<int> x 和适当的原子加载/存储调用。 p>
哦,你也不需要让x volatile。每当您调用(非内联)函数时,该函数或其调用的东西都可以修改全局变量。所以它不能优化x 在while 循环中的读取。并且每个 C++11 同步机制都需要调用一个函数。这恰好调用了内存屏障。