Потокобезопасное напоминание
Позволять'взятьУэс Дайер Подход к функции запоминания в качестве отправной точки:
public static Func Memoize(this Func f)
{
var map = new Dictionary();
return a =>
{
R value;
if (map.TryGetValue(a, out value))
return value;
value = f(a);
map.Add(a, value);
return value;
};
}
Проблема в том, что при использовании его из нескольких потоков мы можем столкнуться с проблемами:
Func f = ...
var f1 = f.Memoize();
...
in thread 1:
var y1 = f1(1);
in thread 2:
var y2 = f1(1);
// We may be recalculating f(1) here!
Позволять'Попытайтесь избежать этого. Блокировка на:map
public static Func Memoize(this Func f)
{
var map = new Dictionary();
return a =>
{
R value;
lock(map)
{
if (map.TryGetValue(a, out value))
return value;
value = f(a);
map.Add(a, value);
}
return value;
};
}
это явно ужасная идея, потому что она мешает нам вычислитьf1
на многихразные аргументы сразу. Блокировка наa
победил'не работает, еслиa
имеет тип значения (и в любом случае это плохая идея, так как мы нет контрольa
и внешний код может заблокировать его тоже).
Вот два варианта, которые я могу придумать:
ПредполагаяLazy
класс для ленивых оценки (см.Вот):
public static Func Memoize(this Func f)
{
var map = new Dictionary();
return a =>
{
Lazy result;
lock(map)
{
if (!map.TryGetValue(a, out result))
{
result = () => f(a);
map.Add(a, result);
}
}
return result.Value;
};
}
Или ведение дополнительного словаря объектов для синхронизации:
public static Func Memoize(this Func f)
{
var map = new Dictionary();
var mapSync = new Dictionary();
return a =>
{
R value;
object sync;
lock(mapSync)
{
if (!mapSync.TryGetValue(a, out sync))
{
sync = new object();
mapSync[a] = sync;
}
}
lock(map)
{
if (map.TryGetValue(a, out value))
return value;
}
lock(sync)
{
value = f(a);
lock(map)
{
map[a] = value;
}
return value;
}
};
}
Есть лучшие варианты?