多线程一次写入、多次读取是否需要易失性

本文关键字:读取 是否 易失性 一次 多线程 | 更新日期: 2023-09-27 17:57:23

这是场景。 我有一个将由多个线程 (ASP.NET) 访问的类,它可以从将结果存储在一次写入多次读取的缓存中中受益。 此缓存对象是操作的结果,该操作不能作为静态初始值设定项的一部分执行,但必须等待第一次执行。 所以我实现了一个简单的空检查,如下所示。 我知道,如果两个线程同时命中此检查,我将计算两次 ExpensiveComputing,但这不是世界末日。 我的问题是,我是否需要担心由于优化或其他线程缓存,静态_cachedResult仍然被其他线程视为空。 写入后,对象只会被读取,因此我认为不需要全面锁定。

public class Bippi
{
   private static ExpensiveCalculation _cachedResult;
   public int DoSomething(Something arg)
   {
      // calculate only once.  recalculating is not harmful, just wastes time.
      if (_cachedResult == null);
         _cachedResult = new ExpensiveCalculation(arg);

      // additional work with both arg and the results of the precalculated
      //    values of _cachedResult.A, _cachedResult.B, and _cachedResult.C
      int someResult = _cachedResult.A + _cachedResult.B + _cachedResult.C + arg.ChangableProp;
      return someResult;
   }
}
public class ExpensiveCalculation
{
   public int A { get; private set; }
   public int B { get; private set; }
   public int C { get; private set; }
   public ExpensiveCalculation(Something arg)
   {
      // arg is used to calculate A, B, and C
   }
}

其他说明,这是在 .NET 4.0 应用程序中。

多线程一次写入、多次读取是否需要易失性

我的问题是,我是否需要担心由于优化或其他线程缓存,静态_cachedResult仍然被其他线程视为空。

是的,你愿意。 这是volatile存在的主要原因之一。

值得一提的是,无争议的锁增加了完全可以忽略不计的性能成本,因此实际上没有理由只lock空检查和资源生成,因为它几乎肯定不会引起任何性能问题,并使程序更容易推理。

最好的解决方案是完全避免这个问题,并使用更高级别的抽象,专门设计用于解决您遇到的确切问题。 在这种情况下,这意味着 Lazy . 您可以创建一个Lazy对象,该对象定义如何创建昂贵的资源,在需要该对象的任何位置访问它,并且Lazy实现负责确保创建资源不超过一次,并且它正确公开给请求所述资源的代码,并且它得到有效处理。

您不需要易失性,您 - 尤其是 - 需要内存屏障,以便处理器缓存同步。

我认为你可以完全乐观地避免锁定,同时避免volatile性能损失。 您可以通过两步方式测试可空性。

  object readonly _cachedResultLock = new object();
  ...
  if (_cachedResult == null)
  {
     lock(_cachedResultLock)
     {
         if (_cachedResult == null)
         {
             _cachedResult = new ExpensiveCalculation(arg);
         }
     }
   }

在这里,大多数时候您不会达到锁定,也不会序列化访问。 您只能在第一次访问时序列化访问 - 但可以保证不会浪费工作(尽管可能会导致另一个线程在第一个线程完成ExpensiveCalculation时稍等片刻)。