如何在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一个潜在问题是此方法可能会在程序中引入死锁。

  1. 你有两个memoized函数_memo1 = Func1.Memoize()_memo2 = Func2.Memoize() ,其中_memo1_memo2是实例变量。
  2. _memo1调用_memo1Func1开始处理。
  3. Thread2调用_memo2 ,在Func2内部调用_memo1和Thread2块。
  4. _memo2Func1的处理在函数的后期调用_memo2_memo2阻塞。
  5. 僵局!

因此,如果可能的话,使用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; }