如何在c#中执行线程安全的函数memoization?
在堆栈溢出这里我发现了记忆单参数函数的代码:
static Func Memoize(this Func f) { var d = new Dictionary(); return a=> { R r; if (!d.TryGetValue(a, out r)) { r = f(a); d.Add(a, r); } return r; }; }
虽然这段代码对我来说很有用,但是当同时从多个线程调用memoized函数时,它会失败: Add
方法被相同的参数调用两次并抛出exception。
如何使memoization线程安全?
你可以使用ConcurrentDictionary.GetOrAdd
来完成你需要的一切:
static Func ThreadsafeMemoize(this Func f) { var cache = new ConcurrentDictionary(); return argument => cache.GetOrAdd(argument, f); }
函数f
本身应该是线程安全的,因为它可以同时从多个线程调用。
此代码也不保证每个唯一参数值只调用一次函数f
。 实际上,它可以在繁忙的环境中多次调用。 如果你需要这种合同,你应该看看这个相关问题的答案,但要注意它们不是那么紧凑并且需要使用锁。
就像Gman提到的那样, ConcurrentDictionary
是执行此操作的首选方法,但是如果这对于简单的lock
语句不可用就足够了。
static Func Memoize(this Func f) { var d = new Dictionary(); 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
是实例变量。 -
_memo1
调用_memo1
,Func1
开始处理。 - Thread2调用
_memo2
,在Func2
内部调用_memo1
和Thread2块。 -
_memo2
对Func1
的处理在函数的后期调用_memo2
,_memo2
阻塞。 - 僵局!
因此,如果可能的话,使用ConcurrentDictionary
,但是如果你不能和你使用锁而不是调用其他Memoized函数,这些函数在你在Memoized函数内运行的函数之外,或者你打开自己的风险死锁(如果_memo1
和_memo2
是局部变量而不是实例变量,则不会发生死锁)。
(注意,使用ReaderWriterLock
可能会略微提高性能,但您仍会遇到相同的死锁问题。)
使用System.Collections.Generic;
Dictionary _description = new Dictionary(); public float getDescription(string value) { string lookup; if (_description.TryGetValue (id, out lookup)) { return lookup; } _description[id] = value; return lookup; }