如何在c#中执行线程安全函数记忆?
在堆栈溢出中,我有一段代码,用于记忆单参数函数:如何在c#中执行线程安全函数记忆?,c#,.net,multithreading,memoization,C#,.net,Multithreading,Memoization,在堆栈溢出中,我有一段代码,用于记忆单参数函数: 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);
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;
};
}
静态函数记忆(此函数)
{
var d=新字典();
返回a=>
{
R;
如果(!d.TryGetValue(a,out r))
{
r=f(a);
d、 加(a,r);
}
返回r;
};
}
虽然这段代码为我完成了它的工作,但当同时从多个线程调用memorized函数时,它有时会失败:Add
方法使用相同的参数调用两次并抛出异常
如何使备忘录线程安全?您可以使用它完成所需的一切:
static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f)
{
var cache = new ConcurrentDictionary<A, R>();
return argument => cache.GetOrAdd(argument, f);
}
static Func,但需要注意的是,它们不够紧凑,需要使用锁。就像Gman提到的concurrentdirectionary
是实现这一点的首选方法,但是如果一个简单的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;
};
}
静态函数记忆(此函数)
{
var d=新字典();
返回a=>
{
R;
锁(d)
{
如果(!d.TryGetValue(a,out r))
{
r=f(a);
d、 加(a,r);
}
}
返回r;
};
}
使用锁而不是ConcurrentDictionary
的一个潜在问题是,这种方法可能会在程序中引入死锁
您有两个记忆化函数\u memo1=Func1.Memoize()
和\u memo2=Func2.Memoize()
,其中\u memo1
和\u memo2
是实例变量
Thread1调用\u memo1
,Func1
开始处理
Thread2调用\u memo2
,在Func2
内部调用\u memo1
和Thread2块
Thread1对Func1
的处理在函数的后期调用\u memo2
,Thread1阻塞
死锁李>
因此,如果可能,请使用ConcurrentDictionary
,但如果您不能使用,而是使用锁,请不要调用其他范围在您正在运行的函数之外的记忆函数(当在记忆函数内部时),否则您将面临死锁的风险(如果\u memo1
和\u memo2
是局部变量而不是实例变量,则不会发生死锁)
(注意,使用ReaderWriterLock
可能会略微提高性能,但仍然会出现相同的死锁问题。)使用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;
}
Dictionary\u description=newdictionary();
公共浮点getDescription(字符串值)
{
字符串查找;
if(_description.TryGetValue(id,out查找)){
返回查找;
}
_描述[id]=值;
返回查找;
}
进一步展开,我想用一个以上的参数来记忆一个函数。下面是我是如何做到的,使用一个C#(需要C#7)作为ConcurrentDictionary
的键
这项技术可以很容易地扩展到允许更多的参数:
public static class FunctionExtensions
{
// Function with 1 argument
public static Func<TArgument, TResult> Memoize<TArgument, TResult>
(
this Func<TArgument, TResult> func
)
{
var cache = new ConcurrentDictionary<TArgument, TResult>();
return argument => cache.GetOrAdd(argument, func);
}
// Function with 2 arguments
public static Func<TArgument1, TArgument2, TResult> Memoize<TArgument1, TArgument2, TResult>
(
this Func<TArgument1, TArgument2, TResult> func
)
{
var cache = new ConcurrentDictionary<(TArgument1, TArgument2), TResult>();
return (argument1, argument2) =>
cache.GetOrAdd((argument1, argument2), tuple => func(tuple.Item1, tuple.Item2));
}
}
公共静态类函数扩展
{
//带1个参数的函数
公共静态函数记忆
(
这个函数
)
{
var cache=新的ConcurrentDictionary();
返回参数=>cache.GetOrAdd(参数,func);
}
//具有2个参数的函数
公共静态函数记忆
(
这个函数
)
{
var cache=新的ConcurrentDictionary();
返回(argument1、argument2)=>
GetOrAdd((argument1,argument2),tuple=>func(tuple.Item1,tuple.Item2));
}
}
例如:
Func<int, string> example1Func = i => i.ToString();
var example1Memoized = example1Func.Memoize();
var example1Result = example1Memoized(66);
Func<int, int, int> example2Func = (a, b) => a + b;
var example2Memoized = example2Func.Memoize();
var example2Result = example2Memoized(3, 4);
Func example1Func=i=>i.ToString();
var example1Memoized=example1Func.Memoize();
var example1Result=Example1emoided(66);
Func example2Func=(a,b)=>a+b;
var example2Memoized=example2Func.Memoize();
var example2Result=Example2emonized(3,4);
(当然,为了获得记忆化的好处,您通常希望将example1emotized
/example2emotized
保存在类变量中或它们不会短暂存在的地方).请注意,GetOrAdd
不会完全阻止对给定参数多次调用f;它只保证只将一个调用的结果添加到字典中。如果线程在添加缓存值之前同时检查缓存,则可以获得多个调用。这是通常不值得担心这一点,但我提到它是为了防止调用产生不必要的副作用。@JamesWorld是的,没错。编辑的答案反映了这一点,谢谢!我有点困惑-cache
这里不是一个局部变量吗?每次ThreadsafeMemoize()
被调用,它不会创建一个新的字典吗?@dashnick是的,但您提供了一个函数,然后返回了一个函数。因此,要记忆每个函数,您只需调用一次ThreadsafeMemoize
,然后使用返回的函数代替您放入的函数。这样,每个记忆函数都会创建一次缓存。如果要使用多个参数记忆函数,您可能会感兴趣。