我的理解是:任何时候一个变量可能在一段代码访问它的控制流之外被改变,这个变量应该被声明为volatile. 信号处理程序、I/O 寄存器和被另一个线程修改的变量都构成了这种情况。
volatile
因此,如果您有一个全局 int foo,并且foo由一个线程读取并由另一个线程以原子方式设置(可能使用适当的机器指令),则读取线程看到这种情况的方式与看到由信号处理程序调整的变量或由外部硬件条件修改,因此foo应该声明volatile(或者,对于多线程情况,使用内存隔离负载访问,这可能是一个更好的解决方案)。
foo
我怎么错了?
多线程上下文的问题volatile在于它不能提供我们需要的 所有 保证。volatile 它确实有一些我们需要的属性,但不是全部,所以我们不能单独 依赖。
但是,我们必须为 其余 属性使用的原语也提供了可以使用的原语volatile,因此实际上没有必要。
对于共享数据的线程安全访问,我们需要保证:
volatile确实保证了第一点。它还保证 在不同的易失性读/写之间 不会发生重新排序。所有volatile内存访问都将按照它们指定的顺序发生。这就是我们所需要的:操作 I/O 寄存器或内存映射硬件,但在多线程代码中,对象通常仅用于同步对非易失性数据的访问,volatile这对我们没有帮助。volatile这些访问仍然可以相对于这些访问重新排序volatile。
防止重新排序的解决方案是使用 内存屏障 ,它向编译器和 CPU 都表明, 在这一点上不能对内存访问进行重新排序 。在我们的 volatile 变量访问周围放置这样的障碍可确保即使是非 volatile 访问也不会在 volatile 访问中重新排序,从而允许我们编写线程安全的代码。
但是,内存屏障 还 确保在达到屏障时执行所有挂起的读/写操作,因此它有效地为我们提供了我们需要的一切,从而变得volatile不必要。我们可以完全删除volatile限定符。
从 C++11 开始,原子变量 ( std::atomic<T>) 为我们提供了所有相关的保证。
std::atomic<T>