.NET4.0:更新字典及其值的线程安全的方式线程、字典、方式、安全

2023-09-07 15:24:02 作者:惯犯.

我有,我想安全地更新静态字典。最初,字典将是空的,但在应用程序的生命周期,它将有添加到它的新值。另外,整数值将采取行动,可以递增和递减的个股。

 私有静态字典<字符串,INT>富=新字典<字符串,INT>();

公共静态无效的添加(串吧)
{
    如果(!foo.ContainsKey(巴))
        foo.Add(酒吧,0);

    富[巴] = foo的[酒吧] + 1;
}


公共静态无效删除(串吧)
{
    如果(foo.ContainsKey(巴))
    {
        如果(FOO [巴]&0)
            富[巴] = foo的[酒吧]  -  1;
    }
}
 

我一直在阅读上的互锁类为手段,以提供线程安全,看来,这可能是东西,我可以使用:

 公共静态无效的添加(串吧)
{
    如果(!foo.ContainsKey(巴))
        foo.Add(酒吧,0);

    Interlocked.Increment(REF富[巴]);
}


公共静态无效删除(串吧)
{
    如果(foo.ContainsKey(巴))
    {
        如果(FOO [巴]&0)
            Interlocked.Decrement(REF富[巴]);
    }
}
 

不过,我的直觉是,这解决不了的时候,我居然添加新项目的字典问题,所以立即锁定想到的:

 私有静态字典<字符串,INT>富=新字典<字符串,INT>();
私有静态对象myLock =新的对象();

公共静态无效的添加(串吧)
{
    锁定(myLock)
    {
        如果(!foo.ContainsKey(巴))
            foo.Add(酒吧,0);

        Interlocked.Increment(REF富[巴]);
    }
}


公共静态无效删除(串吧)
{
    锁定(myLock)
    {
        如果(foo.ContainsKey(巴))
        {
            如果(FOO [巴]&0)
                Interlocked.Decrement(REF富[巴]);
        }
    }
}
 
java的线程安全问题原因及解决办法

这是理想的方式,甚至是正确的?它可以改善?难道我的路要走? 解决方案

锁定好(和必要的;你是对怀疑互锁是不够的),但一旦你做了 Interlocked.Increment Interlocked.Decrement 是不必要的。与访问的问题上词典< TKEY的,TValue> 从多个线程是一个线程可能引发重建内部哈希表中,那么该线程被换出的中期重建的另一个线程现在出现了,增加了词典的词典的内部结构造成破坏。

此外,你的实现是在您锁定 A 私人对象,而不是这个。作为chibacity指出,该锁定对象应该是只读虽然。

你要小心,不要欺骗自己,以为你现在已经取得了你的字典防弹在多线程的情况。例如,以下可能发生:

  

主题1查找字符串你好,世界!在字典中,接收回来的计数 1

     

主题1被换出的线程​​2。

     

主题2电话里删除键你好,世界!计数设置为 0

     

主题2被换出的线程​​1。

     

主题1现在认为,计数 1 ,但它实际上是 0

最后,在.NET 4.0中,你应该考虑使用 ConcurrentDictionary< TKEY的,TValue> 。注意,这有效地消除了需要调用字典实例方法时,但它不发生消除上述情形。

I have a static Dictionary that I want to safely update. Initially, the dictionary will be empty, but over the lifetime of app, it will have new values added to it. Also, the integer values will act as individual counters that can increment and decrement.

private static Dictionary<string, int> foo = new Dictionary<string, int>();

public static void Add(string bar)
{
    if (!foo.ContainsKey(bar))
        foo.Add(bar, 0);

    foo[bar] = foo[bar] + 1;
}


public static void Remove(string bar)
{
    if (foo.ContainsKey(bar))
    {
        if (foo[bar] > 0)
            foo[bar] = foo[bar] - 1;
    }
}

I've been reading up on the Interlocked class as a means to provide thread-safety, and it appears that this may be something that I can use:

public static void Add(string bar)
{
    if (!foo.ContainsKey(bar))
        foo.Add(bar, 0);

    Interlocked.Increment(ref foo[bar]);
}


public static void Remove(string bar)
{
    if (foo.ContainsKey(bar))
    {
        if (foo[bar] > 0)
            Interlocked.Decrement(ref foo[bar]);
    }
}

However, my gut instinct is that this won't solve issues when I'm actually adding new items to the Dictionary, so lock immediately comes to mind:

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

        Interlocked.Increment(ref foo[bar]);
    }
}


public static void Remove(string bar)
{
    lock(myLock)
    {
        if (foo.ContainsKey(bar))
        {
            if (foo[bar] > 0)
                Interlocked.Decrement(ref foo[bar]);
        }
    }
}

Is this approach ideal, or even correct? Can it be improved? Am I way off?

解决方案

lock is good (and necessary; you were right to suspect that Interlocked would not suffice) but once you do that the Interlocked.Increment and Interlocked.Decrement are unnecessary. The issue with accessing a Dictionary<TKey, TValue> from multiple threads is that one thread could trigger a rebuild of the internal hashtable, then that thread gets swapped out mid-rebuild for another thread which now comes along and adds to the dictionary wreaking havoc on the internal structure of the dictionary.

Additionally, your implementation is good in that you lock on a private object rather than this. As chibacity pointed out, this lock object should be readonly though.

Be careful that you don't trick yourself into thinking that you've now made your dictionary bullet-proof in multi-threaded scenarios. For example, the following could happen:

Thread 1 looks up string "Hello, World!" in the dictionary, receives back the count 1.

Thread 1 is swapped out for Thread 2.

Thread 2 calls remove with key "Hello, World!" setting the count to 0.

Thread 2 is swapped out for Thread 1.

Thread 1 now thinks that the count is 1 but it is actually 0.

Finally, in .NET 4.0, you should consider using ConcurrentDictionary<TKey, TValue>. Note that this effectively eliminates the need to lock when calling instance-methods on the dictionary, but it does not eliminate the above scenario from occurring.