它是否被优化完全取决于编译器以及他们选择优化的内容。C++98/03 内存模型无法识别在x
它的设置和值的检索之间可能发生变化的可能性。
C++11 内存模型确实认识到x
可以更改。然而,它并不在乎。对变量的非原子访问(即:不使用std::atomic
s 或适当的互斥体)会产生未定义的行为。因此,对于 C++11 编译器来说,假设x
写入和读取之间永远不会发生变化是完全可以的,因为未定义的行为可能意味着“函数永远不会看到x
变化”。
现在,让我们看看 C++11 对volatile int x;
. 如果你把它放在那里,并且你有一些其他的线程混乱x
,你仍然有未定义的行为。易失性不影响线程行为。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 节中有详细的说明。
让我们看一下您引用的代码,就标准而言。第 1.10 节,p8 谈到了某些库调用使线程与另一个线程“同步”的能力。大多数其他段落解释了同步(和其他事情)如何在线程之间建立操作顺序。当然,您的代码不会调用任何 this。没有同步点,没有依赖排序,什么都没有。
如果没有这种保护,没有某种形式的同步或排序,1.10 p21 就会出现:
如果程序的执行在不同的线程中包含两个相互冲突的操作,则该程序的执行包含数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生。任何此类数据竞争都会导致未定义的行为。
您的程序包含两个相互冲突的操作(读取x
和写入x
)。两者都不是原子的,也不是通过同步命令在另一个之前发生。
因此,您已经实现了未定义的行为。
因此,C++11 内存模型保证多线程行为的唯一情况是使用正确的互斥锁或std::atomic<int> x
使用正确的原子加载/存储调用。
哦,你也不需要变得x
易变。每当您调用(非内联)函数时,该函数或其调用的东西都可以修改全局变量。所以它不能优化循环x
中的读取。while
并且每个 C++11 同步机制都需要调用一个函数。这恰好调用了内存屏障。