多线程一次写入、多次读取是否需要易失性
本文关键字:读取 是否 易失性 一次 多线程 | 更新日期: 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
时稍等片刻)。