将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);
}
答案 0 :(得分:9)
注意:在问题更改为长count
之前,已给出了此答案(包括已编辑的两个)。对于当前问题而不是Thread.VolatileRead
我将使用Interlocked.Read
,它也具有易失性语义,并且还将处理此处讨论的64位读取问题并引入问题
原子读取在没有锁定的情况下得到保证,因为保证了count
的正确对齐的32位或更小值的读取是原子的。
这与64位值不同,如果它从-1开始,并且在另一个线程正在递增时读取,则可能导致读取的值为-1(在增量之前发生),0(在增量之后发生)或者是4294967295或-4294967296(32位写入0,其他32位等待写入)。
Interlocked.Increment
的原子增量意味着整个增量操作是原子的。从概念上考虑增量:
然后如果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)
你的问题的答案是否定的。 lock
和Interlocked
彼此之间没有任何关系,并且它们不会以您建议的方式一起工作。
另外,不确定您的代码是怎么回事。它不编译。您无法锁定值类型。此外,Increment()
采用ref
参数。
答案 4 :(得分:2)
首先,您无法锁定值类型,例如int。
当您锁定值类型时,它首先会被装入一个对象。问题是它每次都会被装箱,每次它都是一个不同的“盒子”。每次都会锁定一个不同的对象,使锁定块无效。
除了这个问题之外,让我们说你锁定了一个引用类型,并在另一个线程上使用了Interlocked类。线程之间不会有同步。当您想要同步时,必须在两个线程上使用相同的机制。