当您将 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);
}
最佳答案
注意:此答案(包括已经进行的两次编辑)是在问题更改为具有较长的count
之前给出的。对于当前问题而不是 Thread.VolatileRead
,我将使用 Interlocked.Read
,它也具有 volatile 语义,并且还将处理此处讨论和介绍的 64 位读取问题进入问题
保证原子读取没有锁定,因为读取正确对齐的 32 位或更少的值(您的 count
是)保证是原子的。
这与 64 位值不同,如果它从 -1 开始,并且在另一个线程递增它时被读取,可能导致读取的值是 -1(发生在递增之前)、0(发生在递增之后)或 4294967295 或 -4294967296(32 位写入 0,其他 32 位等待写入)。
Interlocked.Increment
的原子自增意味着整个自增操作都是原子的。考虑增量是概念上的:
- 读取值。
- 将值加一。
- 写下值(value)。
然后如果 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:确实,您很可能想要这种易变的读取,所以我正在更改上面的答案。您可能可能不会关心它提供的内容,但可能性要小得多。
关于c# - Interlocked 类可以安全地与 lock() 混合使用吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3855904/