• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C# Interlocked 笔记

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

无锁代码下,在读写字段时使用内存屏障往往是不够的。在 64 位字段上进行加、减操作需要使用Interlocked工具类这样更加重型的方式。Interlocked也提供了ExchangeCompareExchange方法,后者能够进行无锁的读-改-写(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访问字段不需要额外的栅栏,除非它们在程序其它地方没有通过Interlockedlock来访问。

Interlocked的数学运算操作仅限于IncrementDecrement以及Add。如果你希望进行乘法或其它计算,在无锁方式下可以使用CompareExchange方法(通常与自旋等待一起使用)。我们会在并行编程中提供一个例子。

Interlocked类通过将原子性的需求传达给操作系统和虚拟机来进行实现其功能。

Interlocked类的方法通常产生 10ns 的开销,是无竞争锁的一半。此外,因为它们不会导致阻塞,所以不会带来上下文切换的开销。然而,如果在循环中多次迭代使用Interlocked,就可能比在循环外使用一个锁的效率低(不过Interlocked可以实现更高的并发度)。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#轻松仿造Vista风格窗体_cici 自娱自乐发布时间:2022-07-10
下一篇:
C#调用非托管代码(转)发布时间:2022-07-10
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap