2013-12-12 23 views
13

这里的堆栈溢出我已经found是memoizes单参数的函数代码:如何在c#中执行线程安全的函数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); 
      d.Add(a, r); 
     } 
     return r; 
    }; 
} 

尽管此代码,它的工作对我来说,它有时会失败时memoized功能同时从多个线程调用: Add方法被相同的参数调用两次并抛出异常。

我怎样才能让记忆化线程安全的?

回答

18

您可以使用ConcurrentDictionary.GetOrAdd它做你需要的一切:

static Func<A, R> ThreadsafeMemoize<A, R>(this Func<A, R> f) 
{ 
    var cache = new ConcurrentDictionary<A, R>(); 

    return argument => cache.GetOrAdd(argument, f); 
} 

功能f应该是线程安全的本身,因为它可以从多个线程同时调用。

此代码还不能保证功能f称为每次唯一参数值只有一次。事实上,在繁忙的环境中,它可以被多次调用。如果你需要这种合同,你应该看看这个related question的答案,但要注意的是它们不是很紧凑,需要使用锁。

+5

请注意,'GetOrAdd'不会完全阻止f被一次给定的参数调用;它只保证只有一个调用的结果被添加到字典中。在线程在添加缓存值之前同时检查缓存的情况下,您可以获得多个调用。这通常不值得担心,但是如果调用具有不需要的副作用,我会提及它。 –

+0

@JamesWorld是的,没错。编辑答案反映,谢谢! – Gman

+1

我有点困惑 - 这里不是'缓存'一个局部变量吗?每次调用ThreadsafeMemoize()时,它不会创建一个新的字典吗? – dashnick

2

像Gman提到的ConcurrentDictionary是这样做的首选方法,但是如果不提供简单的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; 
    }; 
} 

一个潜在的问题,使用锁代替ConcurrentDictionary是这个方法在你的程序导致死锁。

  1. 你有两个memoized功能_memo1 = Func1.Memoize()_memo2 = Func2.Memoize(),其中_memo1_memo2是实例变量。
  2. 线程1调用_memo1Func1开始处理。
  3. 线程2调用_memo2,内部Func2有一个_memo1和Thread2块的调用。
  4. Func1线程1的处理获取到的_memo2呼叫尾盘功能,线程1块。
  5. 死锁!

因此,如果可能的话,使用ConcurrentDictionary,但如果你不能和你使用的锁,而不是不调用的作用域的功能之外,你在里面的时候Memoized功能运行或打开其他Memoized功能自己最多死锁的风险(如果_memo1_memo2了局部变量而不是实例变量的僵局就不会发生)。

System.Collections中使用(注意,性能可能会稍微用ReaderWriterLock改善,但你仍然有相同的僵局问题。)

1

。通用;

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; 
}