假设代码:
class Memory {
Dictionary<int, int> m_values;
Object lockObject = new Object();
public int GetData(int key) {
int result;
lock (lockObject) {
if (!m_values.TryGetValue(key, out result)) {
result = VeryExpensiveComputationMethod(key);
m_values[key] = result;
}
}
return result
}
}
这是安全的,但问题是它不是很有效。有些人想过如何使用与.NET 2.0兼容的代码更好地做到这一点? (在最佳方案中,只有等待相同键的相同结果的线程才应该等待)
答案 0 :(得分:5)
如果您使用ConcurrentDictionary
代替Dictionary
,则可获得两项重要改进:
GetOrAdd
方法,它允许你从字典中获取一个值,或者如果一个人不存在则添加一个值。结合使用Lazy<T>
,它允许您创建一个对象,该对象定义其值作为昂贵计算的结果,它确保仅运行一次,仅在需要时运行,然后缓存并再次返回如果反复询问价值,也会再次。
我们现在可以创建ConcurrentDictionary<int, Lazy<int>>
,在这两种类型之间,它基本上为我们完成了所有工作:
请注意,一旦我们从Lazy
返回GetOrAdd
,就意味着:
在任何情况下都不会为同一个密钥多次调用VeryExpensiveComputationMethod
。
private ConcurrentDictionary<int, Lazy<int>> values =
new ConcurrentDictionary<int, Lazy<int>>();
public int GetData(int key)
{
//Note that this doesn't actually run VeryExpensiveComputationMethod
//until .Value is called on it
var lazy = new Lazy<int>(() => VeryExpensiveComputationMethod(key));
return values.GetOrAdd(key, lazy).Value;
}
就.NET 2.0解决方案而言,您没有任何一种类型。使用Dictionary
和锁定当然是可行的,只是不太干净:
private Dictionary<int, Lazy<int>> values;
private object sync = new object();
public int GetData(int key)
{
Lazy<int> lazy;
lock (sync)
{
if (!values.TryGetValue(key, out lazy))
{
lazy = new Lazy<int>(delegate
{
return VeryExpensiveComputationMethod(key);
});
values.Add(key, lazy);
}
}
return lazy.Value;
}
就Lazy
而言,您只需创建自己的版本:
public delegate T Func<T>();
public class Lazy<T>
{
private object key = new object();
private Func<T> generator;
private T value;
public Lazy(Func<T> generator)
{
this.generator = generator;
}
private volatile bool hasComputedValue;
public bool HasComputedValue { get { return hasComputedValue; } }
public T Value
{
get
{
lock (key)
{
if (HasComputedValue)
return value;
else
{
value = generator();
hasComputedValue = true;
generator = null;
return value;
}
}
}
}
}
答案 1 :(得分:1)
给定.NET 2.0约束的一个简单解决方案是保持Dictionary<int, object>
查找该键的锁对象,并锁定该对象。因此,您的锁定更细粒度,因此支持更多的并发性。也就是说,你需要另一种类型的锁来处理你有一个新看不见的int
的情况。
这样的事情:
internal class Memory
{
public int GetData(int key)
{
int result;
object locker;
// short time lock, blocks all readers
lock (lockObject)
if (!m_locks.TryGetValue(key, out locker))
{
locker = m_locks[key] = new object();
}
// long time lock, but only for readers of this key during expensive op
lock (locker)
if (!m_values.TryGetValue(key, out result))
{
result = m_values[key] = VeryExpensiveComputationMethod(key);
}
return result;
}
private readonly Object lockObject = new Object();
private Dictionary<int, int> m_values;
private Dictionary<int, object> m_locks;
}
答案 2 :(得分:0)
Pure .NET2.0解决方案:
public delegate T Compute<T,TParameter>(TParameter parameter);
public sealed class Lazy<T,TParameter>
{
private T m_Result;
private volatile bool m_IsInitialized;
private object m_SyncRoot = new object();
private Compute<T,TParameter> m_Compute;
private TParameter m_Context;
public Lazy(Compute<T,TParameter> compute, TParameter context)
{
if (compute == null)
{
throw new ArgumentNullException("compute");
}
m_Compute = compute;
m_Context = context;
}
public T Value
{
get
{
if (!m_IsInitialized)
{
lock (m_SyncRoot)
{
if (!m_IsInitialized)
{
m_Result = m_Compute.Invoke(m_Context);
m_Compute = null;
m_Context = default(TParameter);
m_IsInitialized = true;
}
}
}
return m_Result;
}
}
}
class Memory
{
static int VeryExpensiveComputationMethod(int key)
{
return key;
}
private Dictionary<int, Lazy<int,int>> m_Values = new Dictionary<int, Lazy<int,int>>();
private object m_SyncRoot = new object();
public int GetData(int key)
{
Lazy<int,int> result;
lock (m_SyncRoot)
{
if (!m_Values.TryGetValue(key, out result))
{
result = new Lazy<int,int>(VeryExpensiveComputationMethod, key);
m_Values[key] = result;
}
}
return result.Value;
}
}