Подтвердить что ты не робот

Как выполнить потокобезопасную функцию memoization в С#?

Здесь, при переполнении стека, я нашел код, который memoizes функции с одним аргументом:

static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var d = new Dictionary<A, R>();
    return a=> 
    {
        R r;
        if (!d.TryGetValue(a, out r))
        {
            r = f(a);
            d.Add(a, r);
        }
        return r;
    };
}

Хотя этот код выполняет свою работу для меня, он иногда не работает, когда memoized функция вызывается из нескольких потоков одновременно: метод Add вызывается дважды с тем же аргументом и выдает исключение.

Как я могу сделать memoization потокобезопасным?

4b9b3361

Ответ 1

Вы можете использовать ConcurrentDictionary.GetOrAdd, который делает все, что вам нужно:

static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f)
{
    var cache = new ConcurrentDictionary<A, R>();

    return argument => cache.GetOrAdd(argument, f);
}

Функция f должна быть самой потокобезопасной, поскольку она может быть вызвана из нескольких потоков одновременно.

Этот код также не гарантирует, что функция f вызывается только один раз за уникальное значение аргумента. Его можно назвать много раз, по сути, в загруженной среде. Если вам нужен такой контракт, вы должны посмотреть ответы в этом вопросе , но имейте в виду, что они не столь компактны и требуют использования блокировок.

Ответ 2

Как упоминал Гман ConcurrentDictionary, это предпочтительный способ сделать это, однако, если это не доступно для простого оператора lock, будет достаточно.

static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var d = new Dictionary<A, R>();
    return a=> 
    {
        R r;
        lock(d)
        {
            if (!d.TryGetValue(a, out r))
            {
                r = f(a);
                d.Add(a, r);
            }
        }
        return r;
    };
}

Одна потенциальная проблема с использованием блокировок вместо ConcurrentDictionary заключается в том, что этот метод может вводить взаимоблокировки в вашу программу.

  • У вас есть две memoized функции _memo1 = Func1.Memoize() и _memo2 = Func2.Memoize(), где _memo1 и _memo2 являются переменными экземпляра.
  • вызовы Thread1 _memo1, Func1 начинает обработку.
  • Thread2 вызывает _memo2, внутри Func2 есть вызов блоков _memo1 и Thread2.
  • Обработка Thread1 Func1 переходит к вызову _memo2 в конце функции, блоки Thread1.
  • ТУПИК!

Итак, если это вообще возможно, используйте ConcurrentDictionary, но если вы не можете и вы используете блокировки, вместо этого не вызывайте другие Memoized функции, которые ограничены вне функции, в которой вы работаете, когда внутри Memoized функции или вы открываете себя (если _memo1 и _memo2 были локальными переменными вместо переменных экземпляра, тупик бы не произошел).

(Обратите внимание, что производительность может быть немного улучшена с помощью ReaderWriterLock, но у вас все еще будет такая же проблема взаимоблокировки.)

Ответ 3

используя System.Collections.Generic;

Dictionary<string, string> _description = new Dictionary<string, string>();
public float getDescription(string value)
{
     string lookup;
     if (_description.TryGetValue (id, out lookup)) {
        return lookup;
     }

     _description[id] = value;
     return lookup;
}