C# 使缓存的计算结果线程安全的最有效的方法是什么?
(如果有人在其他地方回答了这一问题,我深表歉意;这似乎是一个常见的问题,但事实证明很难找到,因为“线程”和“缓存”等术语会产生压倒性的结果。) 我有一个昂贵的计算,其结果经常被访问,但很少改变。因此,我缓存结果值。下面是我的一些c#伪代码:C# 使缓存的计算结果线程安全的最有效的方法是什么?,c#,.net,multithreading,C#,.net,Multithreading,(如果有人在其他地方回答了这一问题,我深表歉意;这似乎是一个常见的问题,但事实证明很难找到,因为“线程”和“缓存”等术语会产生压倒性的结果。) 我有一个昂贵的计算,其结果经常被访问,但很少改变。因此,我缓存结果值。下面是我的一些c#伪代码: int? _cachedResult = null; int GetComputationResult() { if(_cachedResult == null) { // Do the expensive computat
int? _cachedResult = null;
int GetComputationResult()
{
if(_cachedResult == null)
{
// Do the expensive computation.
_cachedResult = /* Result of expensive computation. */;
}
return _cachedResult.Value;
}
在代码的其他地方,我偶尔会将\u cachedResult
设置回null,因为计算的输入已更改,因此缓存的结果不再有效,需要重新计算。(这意味着我不能使用Lazy
,因为Lazy
不支持重置。)
这对于单线程场景来说很好,但是当然它不是线程安全的。所以我的问题是:使getComputeResult
线程安全的最有效的方法是什么
很明显,我可以把整个东西放在一个lock()块中,但我怀疑有更好的方法吗?(可以进行原子检查以查看结果是否需要重新计算,如果需要,则只锁定?)
非常感谢 您可以使用双重检查锁定模式:
// 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;
}
}
//线程安全(使用双重检查的锁定模式实现性能)
公开课备忘
{
函数计算;
易失性bool\u缓存;
易失性bool\u启动缓存;
volatile StrongBox\u cachedResult;//需要引用类型
对象_cacheSyncRoot=新对象();
公共备忘录(Func计算)
{
_计算=计算;
}
公共价值{
得到{
if(_cached)//快速路径
返回_cachedResult.Value;
锁定(_cacheSyncRoot)
{
如果(!\u缓存)
{
_startedCaching=true;
_cachedResult=新的保险箱(_compute());
_缓存=真;
}
}
返回_cachedResult.Value;
}
}
公共无效
{
如果(!\u启动缓存)
{
//快速路径:已无效
Thread.MemoryBarrier();//需要释放
如果(!\u启动缓存)
返回;
}
锁定(_cacheSyncRoot)
_cached=\u startedCaching=false;
}
}
这个特定的实现与您描述的在紧急情况下应该执行的操作相匹配:如果缓存已失效,则该值只应由单个线程计算一次,其他线程应等待。但是,如果缓存与正在访问的缓存值同时失效,则可能会返回过时的缓存值。这可能会引起一些思考:)
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
}
}
}
使用系统;
使用系统线程;
使用System.Threading.Tasks;
名称空间示例
{
TenReadTimesUpdate类
{
私有任务结果_Task=null;
私有自旋锁自旋锁=新自旋锁(假);
私有TResult LockedFunc(Func locked_Func)
{
TResult t_result=默认值(TResult);
bool-gotLock=false;
if(locked_func==null)返回t_结果;
尝试
{
旋转锁定。输入(参考gotLock);
t_result=locked_func();
}
最后
{
if(gotLock)spin_lock.Exit();
gotLock=false;
}
返回t_结果;
}
公共任务GetComputationAsync()
{
返回
LockedFunc(GetComputationTaskLocked)
;
}
公共T GetComputationResult()
{
返回
LockedFunc(GetComputationTaskLocked)
.结果
;
}
public of TenReadTimesUpdate InvalidateComputingResult()
{
返回
这
.LockedFunc(无效计算结果锁定)
;
}
public of TenReadTimes Update InvalidateComputing ResultLocked()无效
{
结果_任务=null;
归还这个;
}
私有任务GetComputationTaskLocked()
{
if(result_task==null)
{
结果任务=新任务(重计算);
结果_task.Start(TaskScheduler.Default);
}
返回任务的结果;
}
受保护的虚拟T重计算()
{
//繁重的计算
返回默认值(T);//返回一些计算结果
}
}
}
您只需重新分配惰性
即可实现重置:
Lazy<int> lazyResult = new Lazy<int>(GetComputationResult);
public int Result { get { return lazyResult.Value; } }
public void Reset()
{
lazyResult = new Lazy<int>(GetComputationResult);
}
lazyResult=新的Lazy(GetComputationResult);
公共int结果{get{return lazyResult.Value;}}
公共无效重置()
{
lazyResult=新的惰性(GetComputationResult);
}
线程偶尔获得过时值有多糟糕?当一个线程正在计算,而另一个线程想要得到结果时,您预计会发生什么情况?它应该等待还是获取旧值?计算大约需要多少时间?Lazy
无法重置,但可以使用LazyInitializer.EnsureInitiali