Interlocked.CompareExchange真的比简单的锁更快吗?

我遇到了.NET 3.5的ConcurrentDictionary实现(我很抱歉,我现在可以找到链接),它使用这种方法进行锁定:

 var current = Thread.CurrentThread.ManagedThreadId; while (Interlocked.CompareExchange(ref owner, current, 0) != current) { } // PROCESS SOMETHING HERE if (current != Interlocked.Exchange(ref owner, 0)) throw new UnauthorizedAccessException("Thread had access to cache even though it shouldn't have."); 

而不是传统的lock

 lock(lockObject) { // PROCESS SOMETHING HERE } 

问题是:有没有真正的理由这样做? 它更快还是有一些隐藏的好处?

PS:我知道在一些最新版本的.NET中有一个ConcurrentDictionary但我不能用于遗留项目。

编辑

在我的具体情况下,我所做的只是以一种线程安全的方式操作内部Dictionary类。

例:

 public bool RemoveItem(TKey key) { // open lock var current = Thread.CurrentThread.ManagedThreadId; while (Interlocked.CompareExchange(ref owner, current, 0) != current) { } // real processing starts here (entries is a regular `Dictionary` class. var found = entries.Remove(key); // verify lock if (current != Interlocked.Exchange(ref owner, 0)) throw new UnauthorizedAccessException("Thread had access to cache even though it shouldn't have."); return found; } 

正如@doctorlove建议的那样,这是代码: https : //github.com/miensol/SimpleConfigSections/blob/master/SimpleConfigSections/Cache.cs

你的问题没有明确的答案。 我会回答: 这取决于

你提供的代码是:

  1. 等待对象处于已知状态( threadId == 0 == no current work
  2. 做工作
  3. 将已知状态设置回对象
  4. 另一个线程现在也可以工作,因为它可以从第1步到第2步

正如您所指出的,您在代码中有一个实际执行“等待”步骤的循环。 在您可以访问关键部分之前, 不要阻止该线程, 而只是刻录CPU 。 尝试通过Thread.Sleep(2000)替换你的处理(在你的情况下,调用Remove ),你会看到另一个“等待”线程在循环中占用你的所有一个CPU 2s。

这意味着哪一个更好取决于几个因素。 例如:有多少并发访问? 操作需要多长时间才能完成? 你有多少CPU?

我会使用lock而不是Interlocked因为它更容易阅读和维护。 例外的情况是你有一段数百万次的代码,而且你确定Interlocked的特定用例更快。

所以你必须自己衡量两种方法。 如果你没有时间,那么你可能不需要担心表演,你应该使用lock

如果“PROCESS SOMETHING HERE”引发exception,则CompareExchange示例代码不会释放锁定。

出于这个原因以及更简单,更易读的代码,我更喜欢lock语句。

你可以尝试使用try / finally来纠正这个问题,但这会使代码变得更加丑陋。

链接的ConcurrentDictionary实现有一个错误:如果调用者传递一个空键,它将无法释放锁,可能会使其他线程无限期地旋转。

至于效率,您的CompareExchange版本本质上是一个Spinlock ,如果线程很可能在短时间内被阻塞,它可以很有效。 但是插入托管字典可能需要相当长的时间,因为可能需要调整字典的大小。 因此,恕我直言,这不是一个很好的螺旋锁候选者 – 这可能是浪费,特别是在单处理器系统上。

Interlocked课程的文档告诉我们

“为多个线程共享的变量提供primefaces操作。”

理论是primefaces操作可以比锁更快。 Albahari提供了关于互锁操作的更多细节,说明它们更快。

请注意, Interlocked提供了比Lock更“小”的界面 – 请参阅此处的上一个问题

是。 Interlocked类提供primefaces操作,这意味着它们不会像锁一样阻止其他代码,因为它们并不真正需要。 当您锁定一段代码时,您要确保同时没有2个线程,这意味着当一个线程在所有其他线程内部等待进入时,它会使用资源(cpu时间和空闲线程)。 另一方面,primefaces操作不需要阻止其他primefaces操作,因为它们是primefaces操作。 它在概念上是一个CPU操作,接下来只是在前一个之后进入,并且你不会在等待时浪费线程。 (顺便说一下,这就是为什么它仅限于IncrementExchange等非常基本的操作)

我认为一个锁(它是一个下面的监视器)使用了互锁来知道锁是否已经被占用,但是它不能知道它内部的动作可以是primefaces的。

但在大多数情况下,差异并不重要。 但是您需要根据具体情况进行validation。

有点晚了…我已经阅读了你的样本,但简而言之:

最快到最慢的MT同步:

  • Interlocked。* =>这是一个CPUprimefaces指令。 如果它足以满足您的需求,那就无法击败。
  • SpinLock =>使用Interlocked,非常快。 等待时使用CPU。 不要用于等待很长时间的代码(它通常用于防止线程切换以实现快速操作的锁定)。 如果你经常需要等待一个以上的线程周期,我建议你去“锁定”
  • Lock =>比SpinLock更慢但更容易使用和读取。 指令本身非常快,但如果无法获取锁,它将放弃cpu。 在场景后面,它将在内核对象(CriticalSection)上执行WaitForSingleObject,然后只有当获取它的线程释放锁时,Window才会给线程提供cpu时间。

和MT玩得开心!