【发布时间】:2010-11-18 07:21:22
【问题描述】:
让我们以Wes Dyer's的函数记忆方法为出发点:
public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
var map = new Dictionary<A, R>();
return a =>
{
R value;
if (map.TryGetValue(a, out value))
return value;
value = f(a);
map.Add(a, value);
return value;
};
}
问题是,当从多个线程中使用它时,我们可能会遇到麻烦:
Func<int, int> 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<A, R> Memoize<A, R>(this Func<A, R> f)
{
var map = new Dictionary<A, R>();
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<T> 类用于惰性求值(参见 here):
public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
var map = new Dictionary<A, Lazy<R>>();
return a =>
{
Lazy<R> result;
lock(map)
{
if (!map.TryGetValue(a, out result))
{
result = () => f(a);
map.Add(a, result);
}
}
return result.Value;
};
}
或者保留一个额外的对象字典用于同步:
public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
var map = new Dictionary<A, R>();
var mapSync = new Dictionary<A, object>();
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;
}
};
}
还有更好的选择吗?
【问题讨论】:
标签: c# multithreading locking thread-safety memoization