Interlocked类可以安全地与lock()混合使用吗?

时间:2010-10-04 13:56:16

标签: c# .net multithreading

将interlocked操作与lock()(以及其他更高级别的锁)混合时,是否保证原子读取?

我对混合这样的锁定机制以及Int32和Int64之间的任何差异感兴趣。

private Int64 count;
private object _myLock;

public Int64 Count 
{
  get 
  {
    lock(_myLock)
    {
      return count;
    }
  }
}

public void Increment
{
  Interlocked.Increment(ref count);
}

5 个答案:

答案 0 :(得分:9)

注意:在问题更改为长count之前,已给出了此答案(包括已编辑的两个)。对于当前问题而不是Thread.VolatileRead我将使用Interlocked.Read,它也具有易失性语义,并且还将处理此处讨论的64位读取问题并引入问题

原子读取在没有锁定的情况下得到保证,因为保证了count的正确对齐的32位或更小值的读取是原子的。

这与64位值不同,如果它从-1开始,并且在另一个线程正在递增时读取,则可能导致读取的值为-1(在增量之前发生),0(在增量之后发生)或者是4294967295或-4294967296(32位写入0,其他32位等待写入)。

Interlocked.Increment的原子增量意味着整个增量操作是原子的。从概念上考虑增量:

  1. 阅读价值。
  2. 在值中添加一个。
  3. 写下值。
  4. 然后如果x为54并且一个线程试图递增它而另一个线程试图将其设置为67,则两个正确的可能值为67(首先发生增量,然后写入)或68(发生分配)首先,然后递增)但非原子增量可以导致55(增量读取,67的分配发生,增量写入)。

    更常见的实例是x为54,一个线程递增,另一个递减。这里唯一有效的结果是54(一个上升,然后下一个,反之亦然),但如果不是原子的,那么可能的结果是53,54和55.

    如果你只想要一个原子递增的计数,那么正确的代码是:

    private int count;
    
    public int Count 
    {
      get 
      {
        return Thread.VolatileRead(byref count);
      }
    }
    
    public void Increment
    {
      Interlocked.Increment(count);
    }
    

    但是,如果你想对这个数量采取行动,那么它将需要更强的锁定。这是因为使用计数的线程在操作完成之前可能会过时。在这种情况下,您需要锁定关注计数的所有内容以及更改它的所有内容。正是如何做到这一点(以及它是否具有重要意义)取决于您的用例中的更多问题,而不是从您的问题中推断出来。

    编辑:哦,您可能只想锁定以强制内存屏障。您可能还希望将Count的实现更改为return Thread.VolatileRead(ref count);,以确保在要删除锁定时刷新CPU缓存。这取决于在这种情况下缓存过时的重要性。 (另一种方法是使count易变,因为所有的读写操作都是易失的。请注意,Interlocked操作不需要这样做,因为它们总是不稳定的。)

    编辑2:的确,你很可能想要这种不稳定的阅读,我正在改变上面的答案。 可能你不会关心它提供什么,但不太可能。

答案 1 :(得分:3)

提供给lock关键字的参数必须是基于引用类型的对象。在你的代码中,这是值类型,这可能意味着装箱,这使得锁语句无用。

互锁操作相对于在另一个线程中运行的另一个Interlocked操作是原子的,并应用于同一个变量。如果一个线程使用Interlocked操作,则没有线程安全,而另一个线程使用另一个同步算法或没有同步来更改同一个变量。

答案 2 :(得分:3)

  

将interlocked操作与lock()(以及其他更高级别的锁)混合时,是否保证原子读取?

无论是否存在任何类型的锁定,int 的原子读取始终保证 。 C#规范声明int的读取总是原子。

我认为您的实际问题与您提出的问题不同。你能澄清这个问题吗?

答案 3 :(得分:2)

你的问题的答案是否定的。 lockInterlocked彼此之间没有任何关系,并且它们不会以您建议的方式一起工作。

另外,不确定您的代码是怎么回事。它不编译。您无法锁定值类型。此外,Increment()采用ref参数。

答案 4 :(得分:2)

首先,您无法锁定值类型,例如int。

当您锁定值类型时,它首先会被装入一个对象。问题是它每次都会被装箱,每次它都是一个不同的“盒子”。每次都会锁定一个不同的对象,使锁定块无效。

除了这个问题之外,让我们说你锁定了一个引用类型,并在另一个线程上使用了Interlocked类。线程之间不会有同步。当您想要同步时,必须在两个线程上使用相同的机制。