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

标签 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);
}

最佳答案

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

保证原子读取没有锁定,因为读取正确对齐的 32 位或更少的值(您的 count 是)保证是原子的。

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

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

  1. 读取值。
  2. 将值加一。
  3. 写下值(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/

相关文章:

c# - 在 MouseEvent 中获取成员的父类

c# - 操作运行时异步取消

c# - 如何从客户端 JavaScript 函数调用服务器方法背后的代码?

c# - 如何在不违背其目的的情况下为 ParallelQuery 编写 Map-Method?

Windows 套接字 write() 意外被 read() 阻塞

c# - 我如何使用linq优化嵌套的for循环

c# - 如何生成下载文件名?

c# - 将 System.Drawing.Icon 转换为 Microsoft.UI.Xaml.ImageSource

.net - SignalR 集线器可扩展性问题

java - 这个 Java 类是否存在可见性风险或线程安全?