zoukankan      html  css  js  c++  java
  • 【不小心就会犯错】 .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

  • 相关阅读:
    在PHP语言中使用JSON
    PHP数组和Json之间的转换
    Mentohust 安装(win7环境)
    PHP采集网页图片并保存至本地
    php 操作数组 (合并,拆分,追加,查找,删除等)
    GitHub的使用
    【word】含章节号的题注编号以阿拉伯数字显示
    【转载】Mozilla5.0的含义
    同一服务器部署多个Tomcat时的端口号修改详情(同时启动两个Tomcat)
    Android Studio配置
  • 原文地址:https://www.cnblogs.com/PurpleTide/p/2256577.html
Copyright © 2011-2022 走看看