使缓存计算线程的结果安全的最具性能的方法是什么
本文关键字:性能 方法 是什么 结果 缓存 计算 线程 安全 | 更新日期: 2023-09-27 18:20:59
我有一个昂贵的计算,它的结果经常被访问,但很少更改。因此,我缓存得到的值。这里有一些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
线程安全的最具性能的方法是什么?
很明显,我可以把整件事都锁起来(),但我怀疑可能有更好的方法?(它会进行原子检查,看看结果是否需要重新计算,如果需要,只锁定?)
非常感谢!
您可以使用双重检查的锁定模式:
// 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;
}
}
这个特定的实现与您对它在极端情况下应该做什么的描述相匹配:如果缓存已经无效,那么单个线程只应该计算一次值,其他线程应该等待。但是,如果缓存与正在访问的缓存值同时无效,则可能会返回过时的缓存值。
也许这将提供一些思考的食物:)。
- 泛型类
- 该类可以异步或同步计算数据
- 由于自旋锁,允许快速读取
- 不在spinlock中执行繁重的工作,只返回Task,如果需要,在默认的TaskScheduler上创建并启动Task,以避免内联
带有Spinlock的任务是一个非常强大的组合,可以以无锁的方式解决一些问题。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Example
{
class OftenReadSometimesUpdate<T>
{
private Task<T> result_task = null;
private SpinLock spin_lock = new SpinLock(false);
private TResult LockedFunc<TResult>(Func<TResult> locked_func)
{
TResult t_result = default(TResult);
bool gotLock = false;
if (locked_func == null) return t_result;
try
{
spin_lock.Enter(ref gotLock);
t_result = locked_func();
}
finally
{
if (gotLock) spin_lock.Exit();
gotLock = false;
}
return t_result;
}
public Task<T> GetComputationAsync()
{
return
LockedFunc(GetComputationTaskLocked)
;
}
public T GetComputationResult()
{
return
LockedFunc(GetComputationTaskLocked)
.Result
;
}
public OftenReadSometimesUpdate<T> InvalidateComputationResult()
{
return
this
.LockedFunc(InvalidateComputationResultLocked)
;
}
public OftenReadSometimesUpdate<T> InvalidateComputationResultLocked()
{
result_task = null;
return this;
}
private Task<T> GetComputationTaskLocked()
{
if (result_task == null)
{
result_task = new Task<T>(HeavyComputation);
result_task.Start(TaskScheduler.Default);
}
return result_task;
}
protected virtual T HeavyComputation()
{
//a heavy computation
return default(T);//return some result of computation
}
}
}
您可以简单地重新分配Lazy<T>
以实现重置:
Lazy<int> lazyResult = new Lazy<int>(GetComputationResult);
public int Result { get { return lazyResult.Value; } }
public void Reset()
{
lazyResult = new Lazy<int>(GetComputationResult);
}