C# Interlocked 笔记
2016-04-14 18:10
393 查看
无锁代码下,在读写字段时使用内存屏障往往是不够的。在 64 位字段上进行加、减操作需要使用
如果一条语句在底层处理器上被当作一个独立不可分割的指令,那么它本质上是原子的(atomic)。严格的原子性可以阻止任何抢占的可能。对于 32 位(或更低)的字段的简单读写总是原子的。而操作 64 位字段仅在 64 位运行时环境下是原子的,并且结合了多个读写操作的语句必然不是原子的:
在 32 位环境下读写 64 位字段不是原子的,因为它需要两条独立的指令:每条用于对应的 32 位内存地址。所以,如果线程 X 在读一个 64 位的值,同时线程 Y 更新它,那么线程 X 最终可能得到新旧两个值按位组合后的结果(一个撕裂读(torn read))。
编译器实现
抛开内存屏障的事情,你可能会认为如果 10 个线程并发运行
当然,可以通过用
Interlocked工具类这样更加重型的方式。
Interlocked也提供了
Exchange和
CompareExchange方法,后者能够进行无锁的读-改-写(read-modify-write)操作,只需要额外增加一点代码。
如果一条语句在底层处理器上被当作一个独立不可分割的指令,那么它本质上是原子的(atomic)。严格的原子性可以阻止任何抢占的可能。对于 32 位(或更低)的字段的简单读写总是原子的。而操作 64 位字段仅在 64 位运行时环境下是原子的,并且结合了多个读写操作的语句必然不是原子的:
class Atomicity { static int _x, _y; static long _z; static void Test() { long myLocal; _x = 3; // 原子的 _z = 3; // 32位环境下不是原子的(_z 是64位的) myLocal = _z; // 32位环境下不是原子的(_z 是64位的) _y += _x; // 不是原子的 (结合了读和写操作) _x++; // 不是原子的 (结合了读和写操作) } }
在 32 位环境下读写 64 位字段不是原子的,因为它需要两条独立的指令:每条用于对应的 32 位内存地址。所以,如果线程 X 在读一个 64 位的值,同时线程 Y 更新它,那么线程 X 最终可能得到新旧两个值按位组合后的结果(一个撕裂读(torn read))。
编译器实现
x++这种一元运算,是通过先读一个变量,然后计算,最后写回去的方式。考虑如下类:
class ThreadUnsafe { static int _x = 1000; static void Go() { for (int i = 0; i < 100; i++) _x--; } }
抛开内存屏障的事情,你可能会认为如果 10 个线程并发运行
Go,最终
_x会为
0。然而,这并不一定,因为可能存在竞态条件(race condition),在一个线程完成读取
x的当前值,减少值,把值写回这个过程之间,被另一个线程抢占(导致一个过期的值被写回)。
当然,可以通过用
lock语句封装非原子的操作来解决这些问题。实际上,锁如果一致的使用,可以模拟原子性。然而,
Interlocked类为这样简单的操作提供了一个更方便更快的方案:
class Program { static long _sum; static void Main() { // _sum // 简单的自增/自减操作: Interlocked.Increment (ref _sum); // 1 Interlocked.Decrement (ref _sum); // 0 // 加/减一个值: Interlocked.Add (ref _sum, 3); // 3 // 读取64位字段: Console.WriteLine (Interlocked.Read (ref _sum)); // 3 // 读取当前值并且写64位字段 // (打印 "3",并且将 _sum 更新为 10 ) Console.WriteLine (Interlocked.Exchange (ref _sum, 10)); // 10 // 仅当字段的当前值匹配特定的值(10)时才更新它: Console.WriteLine (Interlocked.CompareExchange (ref _sum, 123, 10); // 123 } }
Interlocked上的所有方法都使用全栅栏。因此,通过
Interlocked访问字段不需要额外的栅栏,除非它们在程序其它地方没有通过
Interlocked或
lock来访问。
Interlocked的数学运算操作仅限于
Increment、
Decrement以及
Add。如果你希望进行乘法或其它计算,在无锁方式下可以使用
CompareExchange方法(通常与自旋等待一起使用)。我们会在并行编程中提供一个例子。
Interlocked类通过将原子性的需求传达给操作系统和虚拟机来进行实现其功能。
Interlocked类的方法通常产生 10ns 的开销,是无竞争锁的一半。此外,因为它们不会导致阻塞,所以不会带来上下文切换的开销。然而,如果在循环中多次迭代使用
Interlocked,就可能比在循环外使用一个锁的效率低(不过
Interlocked可以实现更高的并发度)。
相关文章推荐
- C# Timer类详解
- C# get
- C# 单例模式的不同写法对静态变量的影响
- c#XML配置文件辅助类
- C# Socket SSL通讯笔记
- C# DataTable添加行和列
- C# 数组与 list 互相转换案例
- c#概念理解
- 适用于WebForm Mvc的Pager分页组件C#实现
- C#委托与事件的本质区别
- C#中(int)、int.Parse()、int.TryParse()和Convert.ToInt32()的区别
- c#中的保留两位小数并且四舍五入
- C#解析错误代码至错误提示字符串
- C# WinForm 技巧:控件截图
- C#第6周实验类的继承
- C# Dictionary使用
- [C#]exchange发送,收件箱操作类
- C#禁用numericUpDown控件鼠标中键滚轮消息响应
- c# 强制退出程序
- Codeforces 546E Soldier and Traveling 最大流 C#实现