.NET的ConcurrentDictionary,线程安全集合类

ConcurrentDictionary 是.NET 4.0里面新增的号称线程安全的集合类。

那么自然,我们会预期ConcurrentDictionary 中的代码是线程安全的(至少几个关键方法是线程安全的).

举个例子,使用者可能会预期GetOrAdd中的方法当Key不存在的时候只执行一次Add的委托,第二次调用GetOrAdd就应该直接取回刚才生成的值了.

参考一下以下代码:

复制代码
        public static  void Test()
        {
            var concurentDictionary = new ConcurrentDictionary<int, int>();

            var w = new ManualResetEvent(false);
            int timedCalled = 0;
            var threads = new List<Thread>();
            for (int i = 0; i < Environment.ProcessorCount; i++)
            {
                threads.Add(new Thread(() =>
                {
                    w.WaitOne();
                    concurentDictionary.GetOrAdd(1, i1 =>
                    {
                        Interlocked.Increment(ref timedCalled);
                        return 1;
                    });
                }));
                threads.Last().Start();
            }

            w.Set();//release all threads to start at the same time         
            Thread.Sleep(100);
            Console.WriteLine(timedCalled);// output is 4, means call initial 4 times
            //Console.WriteLine(concurentDictionary.Keys.Count);
        }
复制代码

GetOrAdd方法的定义就是按照Key获取一个Value,如果Key不存在,那么调用Func<T> 添加一个键值对.

按照ConcurrentDictionary的定义,  我预期这个Add应该只被调用一次

可是上面那段代码的运行结果表明, Interlocked.Increment(ref timedCalled); 被调用了4次,真是尴尬啊

 

用于初始化值的委托还真的是可以多次执行的,所以

  • 要么保证委托中的代码重复执行不会有问题
  • 要么使用线程安全的初始化方法,例如Lazy<T> 

 

复制代码
 public static void Test()
        {
            var concurentDictionary = new ConcurrentDictionary<int, int>();

            var w = new ManualResetEvent(false);
            int timedCalled = 0;
            var threads = new List<Thread>();
            Lazy<int> lazy = new Lazy<int>(() => { Interlocked.Increment(ref timedCalled); return 1; });
            for (int i = 0; i < Environment.ProcessorCount; i++)
            {
                threads.Add(new Thread(() =>
                {
                    w.WaitOne();
                    concurentDictionary.GetOrAdd(1, i1 =>
                    {
                        return lazy.Value;
                    });
                }));
                threads.Last().Start();
            }

            w.Set();//release all threads to start at the same time         
            Thread.Sleep(100);
            Console.WriteLine(timedCalled);// output is 1
        }
复制代码

 

附: 注释中也不说一下这个初始化方法会被多次调用,如果不是偶然遇到这个问题,估计永远都不知道

复制代码
 //
        // Summary:
        //     Adds a key/value pair to the System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>
        //     if the key does not already exist.
        //
        // Parameters:
        //   key:
        //     The key of the element to add.
        //
        //   valueFactory:
        //     The function used to generate a value for the key
        //
        // Returns:
        //     The value for the key. This will be either the existing value for the key
        //     if the key is already in the dictionary, or the new value for the key as
        //     returned by valueFactory if the key was not in the dictionary.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     key is a null reference (Nothing in Visual Basic).-or-valueFactory is a null
        //     reference (Nothing in Visual Basic).
        //
        //   System.OverflowException:
        //     The dictionary contains too many elements.
复制代码

 

该集合类中所有使用Func<T>的方法也存在类似的问题

希望能给还不知道该问题的朋友提个醒,避免不必要的BUG

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值