作者热门文章
- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
(如果这个问题在其他地方得到了回答,我们深表歉意;这似乎是一个常见问题,但事实证明很难搜索,因为“线程”和“缓存”等术语会产生压倒性的结果。)
我有一个昂贵的计算,其结果被频繁访问但很少更改。因此,我缓存了结果值。这是我的意思的一些 c# 伪代码:
int? _cachedResult = null;
int GetComputationResult()
{
if(_cachedResult == null)
{
// Do the expensive computation.
_cachedResult = /* Result of expensive computation. */;
}
return _cachedResult.Value;
}
在我的代码的其他地方,我偶尔会设置 _cachedResult
回到 null 因为计算的输入已经改变,因此缓存的结果不再有效,需要重新计算。 (这意味着我无法使用 Lazy<T>
,因为 Lazy<T>
不支持重置。)
这适用于单线程场景,但当然它根本不是线程安全的。所以我的问题是:制作 GetComputationResult
的最高效方法是什么?线程安全?
显然我可以把整个东西放在一个 lock() block 中,但我怀疑可能有更好的方法吗? (会进行原子检查以查看结果是否需要重新计算并且仅在需要时才锁定的东西?)
非常感谢!
最佳答案
您可以使用双重检查锁定模式:
// Thread-safe (uses double-checked locking pattern for performance)
public class Memoized<T>
{
Func<T> _compute;
volatile bool _cached;
volatile bool _startedCaching;
volatile StrongBox<T> _cachedResult; // Need reference type
object _cacheSyncRoot = new object();
public Memoized(Func<T> compute)
{
_compute = compute;
}
public T Value {
get {
if (_cached) // Fast path
return _cachedResult.Value;
lock (_cacheSyncRoot)
{
if (!_cached)
{
_startedCaching = true;
_cachedResult = new StrongBox<T>(_compute());
_cached = true;
}
}
return _cachedResult.Value;
}
}
public void Invalidate()
{
if (!_startedCaching)
{
// Fast path: already invalidated
Thread.MemoryBarrier(); // need to release
if (!_startedCaching)
return;
}
lock (_cacheSyncRoot)
_cached = _startedCaching = false;
}
}
这个特定的实现符合你对它在极端情况下应该做什么的描述:如果缓存已经失效,这个值应该只由一个线程计算一次,其他线程应该等待。但是,如果缓存在访问缓存值的同时失效,则可能会返回陈旧的缓存值。
关于c# - 使缓存计算结果线程安全的最高效方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33577081/
我是一名优秀的程序员,十分优秀!