C# 使缓存的计算结果线程安全的最有效的方法是什么?

C# 使缓存的计算结果线程安全的最有效的方法是什么?,c#,.net,multithreading,C#,.net,Multithreading,(如果有人在其他地方回答了这一问题,我深表歉意;这似乎是一个常见的问题,但事实证明很难找到,因为“线程”和“缓存”等术语会产生压倒性的结果。) 我有一个昂贵的计算,其结果经常被访问,但很少改变。因此,我缓存结果值。下面是我的一些c#伪代码: int? _cachedResult = null; int GetComputationResult() { if(_cachedResult == null) { // Do the expensive computat

(如果有人在其他地方回答了这一问题,我深表歉意;这似乎是一个常见的问题,但事实证明很难找到,因为“线程”和“缓存”等术语会产生压倒性的结果。)

我有一个昂贵的计算,其结果经常被访问,但很少改变。因此,我缓存结果值。下面是我的一些c#伪代码:

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;
}
}

这个特定的实现与您描述的在紧急情况下应该执行的操作相匹配:如果缓存已失效,则该值只应由单个线程计算一次,其他线程应等待。但是,如果缓存与正在访问的缓存值同时失效,则可能会返回过时的缓存值。

这可能会引起一些思考:)

  • 泛型类
  • 该类可以异步或同步计算数据
  • 由于自旋锁,允许快速读取
  • 在自旋锁中不执行繁重的操作,只返回任务,必要时在默认TaskScheduler上创建和启动任务,以避免内联
  • 任务和自旋锁是非常强大的组合,可以以无锁的方式解决一些问题

        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