我在许多参考文献中发现它在volatile
C/C++ 中很弱,可能会在多处理器的并发环境中引起问题,但它 ( volatile
) 可以用作 C#/Java 中不同 CPU 之间的通信机制。似乎这个关键字在 C#/Java 中比在 C/C++ 中更严格,但它们之间的区别/影响是什么?
这是volatile
C/C++ 中的参考。
为什么 volatile 在多线程 C 或 C++ 编程中没有用?
我在许多参考文献中发现它在volatile
C/C++ 中很弱,可能会在多处理器的并发环境中引起问题,但它 ( volatile
) 可以用作 C#/Java 中不同 CPU 之间的通信机制。似乎这个关键字在 C#/Java 中比在 C/C++ 中更严格,但它们之间的区别/影响是什么?
这是volatile
C/C++ 中的参考。
为什么 volatile 在多线程 C 或 C++ 编程中没有用?
对于C#/Java, " volatile
" 告诉编译器决不能缓存变量的值,因为它的值可能会在程序本身范围之外发生变化。然后,如果变量“超出其控制范围”更改,编译器将避免任何可能导致问题的优化。
在C/C++中,在开发嵌入式系统或设备驱动程序时需要“ volatile
”,您需要在其中读取或写入内存映射的硬件设备。特定设备寄存器的内容可能随时更改,因此您需要“ volatile
”关键字来确保编译器不会优化此类访问。
volatile 关键字对语言和实现它的平台具有很大的主观性。虽然 Java 在所有架构中都提供了一致的 volatile 行为,但对于直接编译到本机机器平台的语言(例如 C/C++),情况并非如此。让我们试着理解为什么会这样。
设 a、b 是一组程序动作 P 的成员,v_{n} (a) 是对动作应用易失性要求的函数,其中下标 _n 表示应用易失性动作的第 _n 次迭代, 和 \rightarrow 是前面的运算符,这在前面已经解释过了。对于所有程序操作,以下规则成立:
v_n(a) \rightarrow v_{n+1}(a)
a \rightarrow v_n(b) \Rightarrow a \rightarrow v_{n+i}(b) 其中 i \in \mathbb{N}
规则 1 说所有 volatile 函数都强制执行总顺序,其中函数 v_{n} (a) 总是在 v_{n+1} (a) 之前,其中规则 2 说如果一个动作 a 在一个动作的 volatile 函数之前b 在第 _n 次迭代中,则动作 a 必须先于应用于 b 的所有后续 volatile 函数。
这在 Java 中是一个非常强的内存要求,实际上它比 C/C++ 强得多。C/C++ 语言规范对内存排序没有这样的限制,而是让编译器实现来决定如何围绕易失性操作对非易失性操作进行排序。
让我们通过一个简单的代码示例来考虑这些规则如何影响程序执行:
int a = 0;
int b = 0;
volatile int count = 0;
a = 1;
count = 1;
b = 2;
count = 2;
在 C/C++ 中, volatile 关键字仅保证 count 变量不能相互重新排序,即。如果 count == 2,则 count = 1 必须在它之前。但是,既不能保证 a == 1,也不能保证 b == 2。
在 Java 中,给定上面定义的更强保证,那么如果 count == 1,那么断言 a == 1 必须为真。同样,如果 count == 2 则断言 a == 1 && b == 2 必须为真。这就是 Java 提供 C/C++ 不提供的严格内存保证的含义。
然而,这并不意味着 C/C++ 的行为方式与 Java 不同。是否这样做取决于(1)编译器是否执行任何可能以令人惊讶的顺序但合法顺序的代码重新排序,以及(2)底层机器架构是否支持相同的严格内存顺序,前提是编译器不会执行任何令人惊讶的代码重新排序。
例如,在所有 x86 平台上设置 -O0 在 gcc 上编译代码将符合(并且比)Java 的内存模型更严格,但其他架构如 PowerPC、Alpha、Itanium 都支持较弱的内存模型,这可能会出现令人惊讶的程序程序员可能没有预料到的行为。警告讲师!
无论如何,如果您对更多内存模型一致性规则感兴趣,您可能想观看 Intel 对 x86 内存模型的解释,其中详细解释了内存排序的细微差别。享受!
在 C/C++ 中,volatile
没有与多线程相关的特定语义,因此它在该上下文中的行为是特定于平台的。C# 和 Java 为volatile
. 所以你知道你得到了什么并且可以依赖它。