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

时间:2015-11-06 23:18:14

标签: c# .net multithreading

(道歉,如果这在其他地方得到解答;似乎这是一个常见的问题,但事实证明很难搜索,因为像"线程"和#34;缓存&#34这样的术语;产生压倒性的结果。)

我有一个昂贵的计算,其结果经常被访问,但不经常更改。因此,我缓存结果值。这是我的意思的一些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()块中,但我怀疑可能有更好的方法吗? (可以进行原子检查以查看结果是否需要重新计算并且只有锁定的东西?)

非常感谢!

3 个答案:

答案 0 :(得分:4)

您可以使用双重检查锁定模式:

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

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

答案 1 :(得分:0)

或许这会提供一些思考的食物:)。

  1. 通用课程。
  2. 该类可以异步或同步计算数据。
  3. 由于螺旋锁,允许快速读取。
  4. 不执行自旋锁中的繁重内容,只返回Task,如果需要,在默认的TaskScheduler上创建和启动Task,以避免内联。
  5. 使用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
                }
            }
        }
    

答案 2 :(得分:0)

您只需重新分配Html.fromHtml()即可实现重置:

Lazy<T>