并发数据结构:谈谈volatile变量

由来

在CLR 2.0 Memory Model中,我们知道现代CPU架构从CPU到Memory Controller每一级都有速度,容量 不同的高速缓存。之所以这样设计,主要是因为性能。为了进一步提升性能,当线程读取内存中所期望的 元素值时,CPU并不是只读取我们所期望的元素值,它实际上会同时读取该值周围的若干字节,并将其放 入高速缓存中。这是因为应用程序通常读取的字节在内存中彼此相邻。当应用程序又读取该值周围的字节 时,这些字节已经在高速缓存中了,这样就避免了应用程序再次访问内存,也提升了性能。

应用程序在单核CPU的机器上运行时,高速缓存不会有什么影响。但是当应用程序跑在多CPU/多核CPU 的机器上时,我们就要考虑高速缓存所带来的显著影响了(请参考CLR 2.0 Memory Model)。更槽糕的是, C#或JIT编译器编译代码时,会将指令重新排序。因此,应用程序的执行顺序可能会跟编写的顺序不同, 而且现代CPU本身也支持乱序执行CPU指令。

这样,我们就不得不考虑如何来处理高速缓存一致性。不同的CPU处理方式也不尽相同。比如在CLR 2.0 Memory Model中讲到的x86架构的CPU就会维持高速缓存一致性,而x64架构向后兼容x86架构,所以也 有此特性。但是IA64架构的CPU则被设计用来充分利用每个CPU的高速缓存,而且为了提升性能,尽量避免 高速缓存一致性问题。

为了解决高速缓存一致性所带来的问题,CLR在System.Threading.Thread类中提供了若干个下述形式 的静态方法(这是最简单,最原始的方式,所有的锁机制都会强制高速缓存一致性):

static Object VolatileRead(ref Object address);
static Byte VolatileRead(ref Byte address);

static void VolatileWrite(ref Object address, Object value);
static void VolatileWrite(ref Byte address, Byte value);

static void MemoryBarrier();

所有的VolatileRead方法都执行一个包含获取语义的读取操作,这些方法读取由参数address引用的值 ,然后使得CPU高速缓存内的相应字节失效。所有的VolatileWrite方法则执行一个包含释放语义的写入操 作,这些方法将CPU高速缓存内的字节刷到内存中,然后将address参数引用的值修改为value参数所表示 的值。MemoryBarrier方法则执行一个内存栅栏,将CPU高速缓存内的字节刷到内存中,然后使CPU的高速 缓存内的相应字节失效。

C#编译器提供了volatile关键字,该关键字可以用于下述类型的静态/实例字段:byte,sbyte,short ,ushort,int,uint,char,float和bool。此外,我们还可以将volatile关键字应用于引用类型以及枚 举类型的基础类型是byte,sbyte,short,ushot,int,uint,float和bool的枚举字段。volatile关键 字告诉C#和JIT编译器不再在CPU寄存器中缓存字段,从而确保字段的所有读写操作都是对内存的读写, JIT编译器则确保其语义正确,这样就不必显式调用Thread的静态方法VolatileXXX了。

时间: 2024-10-25 21:17:29

并发数据结构:谈谈volatile变量的相关文章

Java并发编程之volatile变量介绍_java

volatile提供了弱同步机制,用来确保将变量更新通知到其它线程.volatile变量不会被缓存在寄存器中或者对其它处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值.可以想象成如下语义,然而volatile是更轻量级的同步机制.volatile只能确保可见性,但不能保证原子性.也就是说不能在复合操作用volatile变量,比如i++. 复制代码 代码如下: public synchronized void setValue(int value){ this.value

并发数据结构:迷人的原子

随着多核CPU成为主流,并行程序设计亦成为研究领域的热门. 要想利用多核/多路CPU带来的强大功能,通常使用多线程来开发应用程序.但是要想拥有良好的硬件 利用率,仅仅简单的在多个线程间分割工作是不够的.还必须确保线程大部分时间在工作,而不是在等待 工作或等待锁定共享数据结构. 在不止一个线程访问共享数据时,所有线程都必须使用同步.如果线程间不进行协调,则没有任务可 以真正并行,更糟糕的是这会给程序带来毁灭性的错误. 现在让我们来看一下在.NET和D语言中的标准同步手段-锁定..NET下我们使用l

使用Volatile变量还是原子变量

volatile变量 在Java语言中,volatile变量提供了一种轻量级的同步机制,volatile变量用来确保将变量的更新操作通知到其它线程,volatile变量不会被缓存到寄存器或者对其它处理器不可见的地方,所以在读取volatile变量时总会返回最新写入的值,volatile变量通常用来表示某个状态标识. 原子变量: 原子变量是"更强大的volatile"变量,从实现来看,每个原子变量类的value属性都是一个volatile变量,所以volatile变量的特性原子变量也有.

转 Java并发编程:volatile关键字解析

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java  5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vol

Java 理论与实践: 正确使用 volatile 变量 线程同步

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比. 这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化. 而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互. 使用建议:在两个或者更多的线程访问的成员变量上使用volatile.当要访问的变量已在synchronized代码块中,或者为常量时,不必使用. 由于使用vo

并发数据结构- 1.8 优先队列&1.9 总结

原文链接,译文链接,译者:郭振斌,校对:周可人 1.8 优先队列 并发的优先队列是一个可线性化到顺序优先队列的数据结构,能够通过常用的优先队列语义提供insert和delete-min操作. 基于堆的优先队列 许多文献中提到的并发优先队列结构,其实是本书前面提到的可线性化堆结构.再一次的,这种结构的基本思想是在个别堆节点上使用细粒度锁,使线程在并行下也能够尽可能的访问数据结构的不同部分.设计这种并发堆的关键问题,在于传统自底向上的insert和自顶向下的delete-min操作有可能造成死锁.B

并发数据结构-1.1.4 复杂度测量&1.1.5 正确性

原文链接,译文链接,译者:张军,校对:周可人 1.1.4 复杂度测量 一个被广泛研究的方向是在理想化模型,如并行随机存取机上分析并发数据结构和算法的渐进复杂度[35, 122, 135].然而,很少有将这些数据结构放在一个真实的多处理器上进行建模的.这里有多种原因,大部分原因跟系统硬件架构与线程异步执行的相互作用有关.想想组合树(combining tree)的例子,虽然我们能通过计算(指令数)得到O(P/logP)的加速比,但这无法反映在实证研究中[52, 129].真实世界的行为是被上述其他

Java 理论与实践: 正确使用 Volatile 变量(转)

Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分.本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形. 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility).互斥即一次只允许一

Java 理论与实践: 正确使用 Volatile 变量

Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分.本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形. 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility).互斥 即一次只允许