2010-10-04 51 views
7

当您将锁定操作与锁定()(和其他更高级别的锁定)混合时,是否保证原子读取?联锁类可以安全地与lock()混合吗?

我感兴趣的是混合类似这样的锁定机制时的一般行为,以及Int32和Int64之间的任何差异。

private Int64 count; 
private object _myLock; 

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

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

切勿锁定'this',该锁将与类锁定对象之外的锁相冲突。只锁定私人引用类型的成员。如果你没有一个适当的私有引用类型的成员,而不是正确类型的静态或实例类型的成员,那么就应该为此目的设置'private object _myLock'''或'private static object _myStaticLock''。 – 2010-10-04 14:27:17

+0

@Jon,注意和编辑 – 2010-10-04 14:30:13

+3

我仍然有这样的感觉,你可能会有比这个问题更大的鱼来炒。就这个问题而言,我很满意我的答案,但它并没有处理线程A基于count进行某些操作的事情,而线程B更改了计数,因为这是否是一个问题,以及如何处理它如果是,取决于你比这里更多。 – 2010-10-04 14:37:52

回答

9

注意:此问题已被更改为count。对于当前的问题,而不是Thread.VolatileRead我会用Interlocked.Read,其中也有挥发性的语义,还将处理这里讨论和引入

原子的读是没有锁定保障问题的64位读的问题,因为读的正确对齐的32位或更小,这你count是,保证是原子的值。

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

原子增量Interlocked.Increment表示整个增量操作是原子的。考虑增量是概念上的:

  1. 阅读值。
  2. 添加一个值。
  3. 写入数值。

然后,如果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:的确,你是如此可能想要这种不稳定的阅读,我正在改变上面的答案。它可能你不会在意它提供什么,但不太可能。

+0

在这种情况下不应该计数被宣布为volatile吗?否则,你最终将注册兑现问题,不是吗? – 2010-10-04 14:44:38

+0

@Martin。 GMTA,正如你所评论的那样补充说。虽然如此,只是我们想要的将取决于一些事情。 VolatileRead将服务于问题中的内容,但是一旦添加了其他操作,“volatile”(根据您的建议)会更好。值得补充的是,Interlocked API具有正确的内存屏障,因此不需要对其进行波动(甚至在您通过volatile字段调用byref时获得的警告文档中也会这样说)。 – 2010-10-04 14:51:46

+0

并且对那些询问http://stackoverflow.com/questions/3853678/why-lock-is-needed-to-implement-a-readonly-int-property/的人表示赞赏,因为它在考虑我对此的回答,我意识到它也适用于这里。 – 2010-10-04 15:01:12

3

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

联锁操作相对于另一个联锁操作在另一个线程中运行,并应用于同一个变量。没有线程安全性,如果一个线程使用互锁操作,另一个使用另一个同步算法或不同步更改同一个变量。

+0

固定代码,对不起,关于 – 2010-10-04 14:08:31

2

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

此外,不知道你的代码是怎么回事。它不会编译。您无法锁定值类型。另外,Increment()需要ref参数。

+0

固定代码,对不起, – 2010-10-04 14:08:24

+0

是的,至少现在会编译。但是肯定的是,这两种类型的锁不会相互同步。此外,你不应该锁定这一点,但这是一个完全不同的讨论。 – Bryan 2010-10-04 14:11:44

+0

现在我想到了,你根本不需要锁上房子。返回一个int是一个原子操作。如果这是你的全部,你可以删除锁(这),你不会有问题。 – Bryan 2010-10-04 14:18:30

2

首先,您不能锁定值类型,如int。

当你锁定一个值类型时,它首先被装箱到一个对象中。问题在于它每次都会装盒,每次都会出现不同的“盒子”。每次您都会锁定不同的对象,导致锁定块无用。

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

+0

固定代码,对不起有关 – 2010-10-04 14:09:21

3

是当你用锁()(和其他更高级别的锁)混合交错操作的原子读取保障?

原子的读的INT总是保证无论任何形式的锁定。整型的读取C#的规范状态是总是原子。

我认为你的实际问题比你问的一个不同。你能澄清这个问题吗?

+0

我打算问一个关于混合隐喻的更一般的问题,编辑相应 – 2010-10-04 14:29:03

相关问题