.NET4.0:ConcurrentDictionary的线程安全更新<TKey,TValue>

Bul*_*nes 5 c# multithreading .net-4.0

这个线程跳出来,我正在尝试使用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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我最初的尝试是:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是正确的用法吗?我会避免以下情况:

  1. 线程1:调用Add("a"),foo ["a"]现在为1.
  2. 线程1被换出线程2.
  3. 线程2:调用Remove("a"),foo ["a"]现在为0.
  4. 线程2被换出线程1.
  5. 线程1:请求foo ["a"]并假设值为1但实际上为0.

Dom*_*nic 1

Add方法并不等效:如果那里不存在任何内容,则原始方法将首先添加0at foo[bar],然后递增,总结果为1at foo[bar]。如果那里不存在,第二个将添加 a 0,并且仅在后续调用中执行增量。

Remove方法并不等效:如果foo.ContainsKey(bar)is ,您的原始方法将不执行任何操作,而第二个方法将为该键false添加值。0

您阅读过 的文档吗AddOrUpdate


编辑:在您编辑解决上述问题后,您的问题的答案更清楚“不,它不会解决您的问题。” 原因在于Remove方法:它的操作现在是非原子的,因为有两个单独的原子操作:TryGetValuefoo[bar] = currValue - 1。另一个线程可能会介入这些操作并导致您担心的不一致。

AddOrUpdate像or 这样的方法的要点GetOrAdd是使公共操作尽可能原子化。不幸的是,他们似乎没有将您的案例原子化ConcurrentDictionary;没有UpdateIfExists

但是,我非常确定以下方法可以解决您的问题:方法Interlocked.Decrement。这解决了以下情况:

  1. 在线程 1 上调用移除;foo[bar]具有值2,该值被存储在 中currValue
  2. 线程 2 接管并Add在那里被调用;foo[bar]增加到3.
  3. 回到线程 1,它设置foo[bar]为 value currValue - 1 = 1

我试图考虑其他可能会失败的案例,但不能......这可能只是意味着我不如其他反对的评论者,尽管:P。

编辑2:我想到了使用的一个问题Interlocked.Decrement:如果它的值为正,它不仅会递减:(。