What is the most efficient way to make cached calculation results by threads?

(I apologize if this was answered elsewhere, it looks like this will be a common problem, but it is hard to find, since terms such as "threading" and "cache" give amazing results.)

I have an expensive computation whose result is often accessed, but rarely. Thus, I cache the resulting value. Here's some C # pseudo code, which I mean:

int? _cachedResult = null;

int GetComputationResult()
{
    if(_cachedResult == null)
    {
        // Do the expensive computation.
        _cachedResult = /* Result of expensive computation. */;
    }
    return _cachedResult.Value;
}

Elsewhere in my code, I sometimes set it _cachedResultback to null because the input to the calculation has changed, and therefore the cached result is no longer valid and needs to be redistributed. (This means that I cannot use Lazy<T>, because it Lazy<T>does not support reset.)

This works great for single-threaded scenarios, but of course it is not entirely safe for threads. So my question is: what is the most efficient way to make it GetComputationResultthread safe?

Obviously, I could just put all this in a blocking block (), but I suspect there might be a better way? (Something that an atomic test would do to see if you need to recalculate the result and only block if that happens?)

Thanks a lot!

+4
3

:

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

, : , , , . , , .

+4

, :).

  • .
  • .
  • -.
  • -, Task TaskScheduler, inlining.

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

You can simply reassign Lazy<T>to achieve reset:

Lazy<int> lazyResult = new Lazy<int>(GetComputationResult);

public int Result { get { return lazyResult.Value; } }

public void Reset()
{
   lazyResult = new Lazy<int>(GetComputationResult);
}
0
source

Source: https://habr.com/ru/post/1614817/


All Articles