对 Interlocked.Increment 和 Lock 的使用感到困惑

Posted

技术标签:

【中文标题】对 Interlocked.Increment 和 Lock 的使用感到困惑【英文标题】:confused on usage of Interlocked.Increment and Lock 【发布时间】:2012-09-07 09:11:34 【问题描述】:

我了解Interlocked.Incrementlock() 的功能。但我对何时使用其中一种感到困惑。据我所知,Interlocked.Increment 增加了共享 int/long 值,而lock() 旨在锁定代码区域。

例如,如果我想更新字符串值,可以使用lock()

lock(_object)

    sharedString = "Hi";

但是,Interlocked 类无法做到这一点。

为什么不能通过Interlocked 完成? 这些同步机制有什么区别?

【问题讨论】:

或许这篇博文会有所帮助:geekswithblogs.net/BlackRabbitCoder/archive/2012/09/06/… 【参考方案1】:

Interlocked.Increment 及相关方法依靠硬件指令对单个 32bit 或 64bit 内存值进行同步修改,确保多个线程访问同一个值时不会读/写陈旧数据。这是必要的,因为在硬件级别,处理器具有内存值的本地/总线副本(出于性能考虑,通常称为总线内存或 cpu 缓存)。

lock() 为一段代码执行同步,而不是单个整数值。而不是依靠硬件指令来同步对变量的访问,结果代码而是依靠操作系统同步原语(软件,而不是硬件)来保护内存和代码执行。

此外,lock() 的使用会发出内存屏障,确保从多个 CPU 访问相同的变量会产生同步(非陈旧)数据。在其他必须明确执行内存屏障和防护的语言/平台中,情况并非如此。

在整数值上使用Interlocked 方法效率更高,因为硬件本身支持执行必要的同步。但是这种硬件支持只存在于 __int32 和 __int64 等本机积分,因为硬件没有更高级别的复杂类型的概念,因此Interlocked 类型没有公开这种高级方法。因此,您不能使用 Interlocked 同步分配 System.String 或任何 System.Object 派生类型。

(即使如果您使用的是较低级别的语言,也可以使用相同的硬件指令来分配指向字符串值的指针,但事实是,在 .NET 中,字符串对象并不表示为指针,因此它在任何“纯”.NET 语言中都是不可能的。我正在避免这样一个事实,即您可以使用不安全的方法来解析指针并执行字符串值的互锁分配,如果您 - 真的 - 想要,但我不'感觉这并不是您真正要问的问题,而且 Interlocked 不支持这一点,因为需要在后台进行 GC 固定,这可能会比使用 lock() 更昂贵和更具侵入性。)

因此,对于“引用类型”的同步修改/分配,您将需要使用同步原语(即 lock()、Monitor 等)。如果您只需要同步一个整数值(Int32、Int64),那么使用 Interlocked 方法会更有效。如果有多个整数值要同步,则使用 lock() 语句可能仍然有意义,例如递增一个整数同时递减第二个整数,这两个整数都需要作为单个逻辑操作进行同步。

【讨论】:

【参考方案2】:

Interlocked.Increment 可以而且应该用于增加共享的int 变量。 在功能上使用Interlocked.Increment 与:

lock(_object)

   counter++;

Interlocked.Increment 在性能方面要便宜得多。

【讨论】:

"但是Interlocked.Increment 在性能方面要便宜得多。"为什么后面是一样的呢? @Guillaume 不,它们不一样,我说它们在功能上相似(产生相同的结果) 查看this answer了解差异详情。【参考方案3】:

如果要交换引用值,并在原子操作中返回原始值,可以使用Interlocked.ExchangeInterlocked.Increment 完全按照它所说的去做:它递增一个数字。

但简单地将引用值分配给变量,或者任何 32 位值类型在 .NET 中都是原子的。我能想到的唯一另一种情况,后者不成立,如果你创建一个打包结构并设置属性,这将迫使编译器不在 4 字节边界对齐成员(但这不是你做的事情真的经常)。

【讨论】:

您能否详细说明一下,那个不正确的状态是什么?至于“明显错误”,请查看language specs,第 12.5 章:*“以下数据类型的读写应是原子的:(...)、int、(...) 和引用类型."。不,我很确定它们是原子的。 当然不是,但没人这么说。读取和写入是原子的,但您所说的是两个不同的操作,而这正是我在第一段中所写的。但是如果你看一下OP的问题,你会发现它只不过是一个引用赋值,它是原子的,不需要同步。如果一个线程想将一个字符串字段设置为Hi,另一个设置为Hello,则在此过程中没有任何东西可以“损坏”。 另外,它与多核系统无关,单核系统使用多线程没有问题,不同步也会出现同样的问题。您应该再次阅读 OP 的问题,并看到他的意图不是增加数字,而是“更新 string 值”。以它呈现的形式,不,不需要任何类型的同步构造。

以上是关于对 Interlocked.Increment 和 Lock 的使用感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

如果 Interlocked.Increment 是原子的,为啥我应该使用 ++ 代替?

Interlocked.Increment 溢出会导致 .NET 运行时损坏吗?

使用Interlocked.Increment的C#对象池

SQL Server 中的 NEXT VALUE FOR @Sequence 的工作方式是不是与 C# 中的 Interlocked.Increment() 相同? [复制]

信号灯 ManualResetEvent 与 Interlocked.Increment 原子操作使

Interlocked