Skip to content

C# 多线程 06-使用并发集合 01-使用 ConcurrentDictionary

🏷️ 《C# 多线程》

使用 ConcurrentDictionary

csharp
const string Item = "Dictionary item";
const int Iterations = 1000000;
public static string CurrentItem;

static void Main(string[] args)
{
    var concurrentDictionary = new ConcurrentDictionary<int, string>();
    var dictionary = new Dictionary<int, string>();

    var sw = new Stopwatch();

    sw.Start();
    for (int i = 0; i < Iterations; i++)
    {
        lock (dictionary)
        {
            dictionary[i] = Item;
        }
    }
    sw.Stop();
    Console.WriteLine($"写入使用粗粒度锁的字典: {sw.Elapsed}");

    sw.Restart();
    for (int i = 0; i < Iterations; i++)
    {
        concurrentDictionary[i] = Item;
    }
    sw.Stop();
    Console.WriteLine($"写入一个 ConcurrentDictionary(细粒度锁): {sw.Elapsed}");

    sw.Restart();
    for (int i = 0; i < Iterations; i++)
    {
        lock (dictionary)
        {
            CurrentItem = dictionary[i];
        }
    }
    sw.Stop();
    Console.WriteLine($"使用粗粒度锁从字典中读取: {sw.Elapsed}");

    sw.Restart();
    for (int i = 0; i < Iterations; i++)
    {
        CurrentItem = concurrentDictionary[i];
    }
    sw.Stop();
    Console.WriteLine($"从一个 ConcurrentDictionary 中读取: {sw.Elapsed}");


    Console.WriteLine();


    int taskSize = 10;
    Task[] ts = new Task[taskSize];
    for (int j = 0; j < taskSize; j++)
    {
        Task t = new Task(() => {
            for (int i = 0; i < Iterations; i++)
            {
                lock (dictionary)
                {
                    dictionary[i] = Item;
                }
            }
        });
        ts[j] = t;
    }
    var whenAllTask = Task.WhenAll(ts);
    sw.Restart();
    for (int i = 0; i < ts.Length; i++)
    {
        ts[i].Start();
    }
    whenAllTask.Wait();
    sw.Stop();
    Console.WriteLine($"【多线程】写入使用粗粒度锁的字典: {sw.Elapsed}");

    
    ts = new Task[taskSize];
    for (int j = 0; j < taskSize; j++)
    {
        Task t = new Task(() => {
            for (int i = 0; i < Iterations; i++)
            {
                concurrentDictionary[i] = Item;
            }
        });
        ts[j] = t;
    }
    whenAllTask = Task.WhenAll(ts);
    sw.Restart();
    for (int i = 0; i < ts.Length; i++)
    {
        ts[i].Start();
    }
    whenAllTask.Wait();
    sw.Stop();
    Console.WriteLine($"【多线程】写入一个 ConcurrentDictionary(细粒度锁): {sw.Elapsed}");
    

    ts = new Task[taskSize];
    for (int j = 0; j < taskSize; j++)
    {
        Task t = new Task(() => {
            for (int i = 0; i < Iterations; i++)
            {
                lock (dictionary)
                {
                    CurrentItem = dictionary[i];
                }
            }
        });
        ts[j] = t;
    }
    whenAllTask = Task.WhenAll(ts);
    sw.Restart();
    for (int i = 0; i < ts.Length; i++)
    {
        ts[i].Start();
    }
    whenAllTask.Wait();
    sw.Stop();
    Console.WriteLine($"【多线程】使用粗粒度锁从字典中读取: {sw.Elapsed}");
    

    ts = new Task[taskSize];
    for (int j = 0; j < taskSize; j++)
    {
        Task t = new Task(() => {
            for (int i = 0; i < Iterations; i++)
            {
                CurrentItem = concurrentDictionary[i];
            }
        });
        ts[j] = t;
    }
    whenAllTask = Task.WhenAll(ts);
    sw.Restart();
    for (int i = 0; i < ts.Length; i++)
    {
        ts[i].Start();
    }
    whenAllTask.Wait();
    sw.Stop();
    Console.WriteLine($"【多线程】从一个 ConcurrentDictionary 中读取: {sw.Elapsed}");
    
    Console.ReadLine();
}

运行结果

运行会得到一组比较诡异的结果:

txt
写入使用粗粒度锁的字典: 00:00:00.0558413
写入一个ConcurrentDictionary(细粒度锁): 00:00:00.3505142
使用粗粒度锁从字典中读取: 00:00:00.0281449
从一个ConcurrentDictionary中读取: 00:00:00.0227328

【多线程】写入使用粗粒度锁的字典: 00:00:00.3331349
【多线程】写入一个ConcurrentDictionary(细粒度锁): 00:00:00.1801702
【多线程】使用粗粒度锁从字典中读取: 00:00:00.3150063
【多线程】从一个ConcurrentDictionary中读取: 00:00:00.1925646

使用 lock 关键字时的写入操作,多线程的耗时明显比单线程多很多(55ms => 333ms),因为单线程只迭代了 100w 次,而多线程时开启了 10 个线程,总共迭代了 1000w 次,耗时增加很容易理解;

使用 ConcurrentDictionary 类时的写入操作,多线程的耗时反而比单线程时少了(350ms => 180ms),即便是迭代次数是 10 倍的情况下。

读取操作不管是单线程还是多线程,使用 ConcurrentDictionary 类的效率均是稍微高一些。

总结

如果是读取多而写入少的情况,始终推荐使用 ConcurrentDictionary 类;

写入多时,如果是多线程则推荐使用 ConcurrentDictionary 类;单线程则直接使用 Dictionary 类就可以了(不需要使用 lock 关键字,上面的例子仅仅是为了演示)。