为什么 volatile 在多线程 C 或 C++ 编程中不被认为有用? [英] Why is volatile not considered useful in multithreaded C or C++ programming?

查看:37
本文介绍了为什么 volatile 在多线程 C 或 C++ 编程中不被认为有用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如我最近发布的这个答案所示,我似乎对 volatile 在多线程编程上下文中的实用性(或缺乏)感到困惑.

As demonstrated in this answer I recently posted, I seem to be confused about the utility (or lack thereof) of volatile in multi-threaded programming contexts.

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

My understanding is this: any time a variable may be changed outside the flow of control of a piece of code accessing it, that variable should be declared to be volatile. Signal handlers, I/O registers, and variables modified by another thread all constitute such situations.

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

So, if you have a global int foo, and foo is read by one thread and set atomically by another thread (probably using an appropriate machine instruction), the reading thread sees this situation in the same way it sees a variable tweaked by a signal handler or modified by an external hardware condition and thus foo should be declared volatile (or, for multithreaded situations, accessed with memory-fenced load, which is probably a better a solution).

我哪里错了?

推荐答案

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

The problem with volatile in a multithreaded context is that it doesn't provide all the guarantees we need. It does have a few properties we need, but not all of them, so we can't rely on volatile alone.

然而,我们必须用于剩余属性的原语也提供了volatile所做的那些,因此实际上是不必要的.

However, the primitives we'd have to use for the remaining properties also provide the ones that volatile does, so it is effectively unnecessary.

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

For thread-safe accesses to shared data, we need a guarantee that:

  • 读/写实际上发生了(编译器不会只是将值存储在寄存器中,而是将主内存的更新推迟到很晚之后)
  • 不会发生重新排序.假设我们使用 volatile 变量作为标志来指示是否准备好读取某些数据.在我们的代码中,我们只是在准备数据后设置标志,所以一切看起来都很好.但是如果指令被重新排序以便标志被设置first怎么办?
  • the read/write actually happens (that the compiler won't just store the value in a register instead and defer updating main memory until much later)
  • that no reordering takes place. Assume that we use a volatile variable as a flag to indicate whether or not some data is ready to be read. In our code, we simply set the flag after preparing the data, so all looks fine. But what if the instructions are reordered so the flag is set first?

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

volatile does guarantee the first point. It also guarantees that no reordering occurs between different volatile reads/writes. All volatile memory accesses will occur in the order in which they're specified. That is all we need for what volatile is intended for: manipulating I/O registers or memory-mapped hardware, but it doesn't help us in multithreaded code where the volatile object is often only used to synchronize access to non-volatile data. Those accesses can still be reordered relative to the volatile ones.

防止重新排序的解决方案是使用内存屏障,这向编译器和 CPU 指示在此点上不能对任何内存访问进行重新排序.在我们的 volatile 变量访问周围放置这样的屏障确保即使是非 volatile 访问也不会跨 volatile 重新排序,从而允许我们编写线程安全代码.

The solution to preventing reordering is to use a memory barrier, which indicates both to the compiler and the CPU that no memory access may be reordered across this point. Placing such barriers around our volatile variable access ensures that even non-volatile accesses won't be reordered across the volatile one, allowing us to write thread-safe code.

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

However, memory barriers also ensure that all pending reads/writes are executed when the barrier is reached, so it effectively gives us everything we need by itself, making volatile unnecessary. We can just remove the volatile qualifier entirely.

自 C++11 起,原子变量 (std::atomic) 为我们提供了所有相关保证.

Since C++11, atomic variables (std::atomic<T>) give us all of the relevant guarantees.

这篇关于为什么 volatile 在多线程 C 或 C++ 编程中不被认为有用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆