• 【不小心就会犯错】 .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

  • 相关阅读:
    面向对象的三个基本特征(讲解)
    GridView 72般绝技
    Asp.net 将数据库里的记录转换成json
    jquery json asp.net 将各种对象:list ..等转换成
    sql2000 分页存储过程
    .NET中DataSet转化Json工具类
    从攻击者痕迹看内网常见命令
    从攻击者角度看SetMpreference小结
    Java NIO 实现服务端和客户端的通信示例
    spark streaming 监听器执行顺序
  • 原文地址:https://www.cnblogs.com/PurpleTide/p/2256577.html
Copyright © 2020-2023  润新知