小编典典

为什么 volatile 在多线程 C 或 C++ 编程中没有用?

all

我的理解是:任何时候一个变量可能在一段代码访问它的控制流之外被改变,这个变量应该被声明为volatile. 信号处理程序、I/O
寄存器和被另一个线程修改的变量都构成了这种情况。

因此,如果您有一个全局 int
foo,并且foo由一个线程读取并由另一个线程以原子方式设置(可能使用适当的机器指令),则读取线程看到这种情况的方式与看到由信号处理程序调整的变量或由外部硬件条件修改,因此foo应该声明volatile(或者,对于多线程情况,使用内存隔离负载访问,这可能是一个更好的解决方案)。

我怎么错了?


阅读 58

收藏
2022-08-15

共1个答案

小编典典

多线程上下文的问题volatile在于它不能提供我们需要的 所有 保证。volatile
它确实有一些我们需要的属性,但不是全部,所以我们不能单独 依赖。

但是,我们必须为 其余 属性使用的原语也提供了可以使用的原语volatile,因此实际上没有必要。

对于共享数据的线程安全访问,我们需要保证:

  • 读/写实际上发生了(编译器不会只是将值存储在寄存器中,而是将更新主内存推迟到很久以后)
  • 不会发生重新排序。假设我们使用一个volatile变量作为标志来指示某些数据是否准备好被读取。在我们的代码中,我们只是在准备好数据后设置了标志,所以一切 看起来都 很好。但是如果指令被重新排序以便 首先 设置标志怎么办?

volatile确实保证了第一点。它还保证 在不同的易失性读/写之间
不会发生重新排序。所有volatile内存访问都将按照它们指定的顺序发生。这就是我们所需要的:操作 I/O
寄存器或内存映射硬件,但在多线程代码中,对象通常仅用于同步对非易失性数据的访问,volatile这对我们没有帮助。volatile这些访问仍然可以相对于这些访问重新排序volatile

防止重新排序的解决方案是使用 内存屏障 ,它向编译器和 CPU 都表明, 在这一点上不能对内存访问进行重新排序 。在我们的 volatile
变量访问周围放置这样的障碍可确保即使是非 volatile 访问也不会在 volatile 访问中重新排序,从而允许我们编写线程安全的代码。

但是,内存屏障
确保在达到屏障时执行所有挂起的读/写操作,因此它有效地为我们提供了我们需要的一切,从而变得volatile不必要。我们可以完全删除volatile限定符。

从 C++11 开始,原子变量 ( std::atomic<T>) 为我们提供了所有相关的保证。

2022-08-15