c# - 在多线程代码中读取 .NET Int32 时,我们需要锁定它吗?

标签 c# .net multithreading locking

我正在阅读以下文章: http://msdn.microsoft.com/en-us/magazine/cc817398.aspx “解决多线程代码中的 11 个可能问题”,作者:Joe Duffy

它向我提出了一个问题: “在多线程代码中读取 .NET Int32 时,我们需要锁定它吗?”

我知道如果它是 32 位 SO 中的 Int64,它可能会撕裂,如文章中所述。但是对于 Int32 我想象了下面的情况:

class Test
{
  private int example = 0;
  private Object thisLock = new Object();

  public void Add(int another)
  {
    lock(thisLock)
    {
      example += another;
    }
  }

  public int Read()
  {
     return example;
  }
}

我看不到在 Read 方法中包含锁的理由。你呢?

更新 根据答案(来自 Jon Skeet 和 ctacke),我了解到上面的代码仍然容易受到多处理器缓存的影响(每个处理器都有自己的缓存,与其他处理器不同步)。以下所有三个修改都解决了这个问题:

  1. 向“int example”添加“volatile”属性
  2. 插入一个 Thread.MemoryBarrier();在实际阅读“int example”之前
  3. 读取“锁(thisLock)”中的“int example”

而且我还认为“volatile”是最优雅的解决方案。

最佳答案

锁定完成两件事:

  • 它充当互斥锁,因此您可以确保一次只有一个线程修改一组值。
  • 它提供内存屏障(获取/释放语义),确保一个线程进行的内存写入在另一个线程中可见。

大多数人理解第一点,但不理解第二点。假设您从两个不同的线程使用问题中的代码,一个线程重复调用 Add,另一个线程调用 Read。原子性本身将确保您最终只读取 8 的倍数——如果有两个线程调用 Add,您的锁将确保您不会“丢失”任何添加。但是,您的 Read 线程很可能只会读取 0,即使在多次调用 Add 之后也是如此。在没有任何内存屏障的情况下,JIT 可以将值缓存在寄存器中并假定它在两次读取之间没有改变。内存屏障的目的是确保某些内容确实写入了主内存,或者确实从主内存中读取。

内存模型可能会变得非常棘手,但如果您遵循每次想要访问共享数据(读写)时取出锁的简单规则,您就没事了。查看volatility/atomicity有关详细信息,请参阅我的线程教程的一部分。

关于c# - 在多线程代码中读取 .NET Int32 时,我们需要锁定它吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/395232/

相关文章:

c++ - 即使使用 sleep ,线程也不会放弃 CPU 时间

c - 向设备发送数据时 UNIX read()/write() 的原子性

c# - session 状态变量不起作用

c# - Visual Studio 2017 代码编译,但 nuget 包带有红色下划线

c# - 我应该将 XSLT 文件放在我的 ASP.NET Web 应用程序的 App_Data 文件夹中吗?

c# - Accpac API 文档

c# - WCF 是否进行 CRL/OCSP 证书检查?

C#多线程读取不可修改的集合

c# - 拍摄照片后无法导航到另一个页面

C#模拟鼠标滚轮向下