从this thread跳出来,我正在尝试使用ConcurrentDictionary来复制以下内容:
public static class Tracker
{
private static Dictionary<string, int> foo = new Dictionary<string, int>();
private static object myLock = new object();
public static void Add(string bar)
{
lock(myLock)
{
if (!foo.ContainsKey(bar))
foo.Add(bar, 0);
foo[bar] = foo[bar] + 1;
}
}
public static void Remove(string bar)
{
lock(myLock)
{
if (foo.ContainsKey(bar))
{
if (foo[bar] > 0)
foo[bar] = foo[bar] - 1;
}
}
}
}
我最初的尝试是:
public static class Tracker2
{
private static ConcurrentDictionary<string, int> foo =
new ConcurrentDictionary<string, int>();
public static void Add(string bar)
{
foo.AddOrUpdate(bar, 1, (key, n) => n + 1);
}
public static void Remove(string bar)
{
// Adding a 0'd item may be ok if it wasn't there for some reason,
// but it's not identical to the above Remove() implementation.
foo.AddOrUpdate(bar, 0, (key, n) => (n > 0) ? n - 1 : 0);
}
}
这是正确的用法吗?我会避免以下情况:
答案 0 :(得分:3)
使用ConcurrentDictionary
无法避免您担心的不一致。你需要更多,更强大和更强大的东西来保证这一点。在开始解决问题之前,问问自己是否真的需要这种程度的一致性。龙在那里。
重复一点自己:ConcurrentDictionary
只能确保点击字典的多个线程不会破坏它。当多个线程连续拉动时,它不保证值的一致性。它不能阻止你在脚下射击自己。
答案 1 :(得分:1)
Add
方法不等效:如果0
原始文件中存在任何内容,您的原始文件将首先添加foo[bar]
,然后递增,1
的总结果为{ {1}}。如果那里没有任何内容,第二个将添加foo[bar]
,并且只会在后续调用中执行增量。
0
方法不等效:如果Remove
为foo.ContainsKey(bar)
,则原始文件无效,而第二个方法将为该密钥添加false
值。
您是否阅读了0
的文档?
编辑:在您编辑以解决上述问题之后,您的问题的答案更清楚“不,它不会解决您的问题”。原因是AddOrUpdate
方法:它的操作现在是非原子的,因为有两个独立的原子操作:Remove
和TryGetValue
。另一个线程可能介入这些操作之间并导致您担心的不一致。
像foo[bar] = currValue - 1
或AddOrUpdate
这样的方法的目的是使公共操作尽可能原子化。不幸的是,看起来他们没有用GetOrAdd
原子化你的案例;没有ConcurrentDictionary
。
但是,我很确定以下将解决您的问题:the Interlocked.Decrement
method。这解决了以下情况:
UpdateIfExists
的值为foo[bar]
,存储在2
。currValue
; Add
会增加到foo[bar]
。3
设置为值foo[bar]
。我试着想到其他可能会破裂的案例,但不能......这可能只是意味着我不如其他反对意见的人那么好:P。
编辑2:我想到使用currValue - 1 = 1
时遇到的问题:如果它的值为正,它不仅会减少:(。
答案 2 :(得分:1)
简短回答:不,并发字典不会保护你描述的序列。
但话说回来,你的早期代码也不会。
如果需要保证集合中的对象在一个线程上的一系列操作期间保持不变,请将该对象分配给线程中的局部变量,并仅使用局部变量。然后你的线程代码不关心集合中对象会发生什么。