ConcurrentDictionary<>在一个线程误解的表现?

时间:2013-03-06 15:58:50

标签: c# .net-4.0 task-parallel-library concurrentdictionary

相关简介信息:

AFAIK,并发堆栈,队列和包类在内部使用链接列表实现 而且我知道争用少得多,因为每个线程都负责自己的链表。 无论如何,我的问题是关于ConcurrentDictionary<,>

但是我正在测试这段代码:(单线程)

Stopwatch sw = new Stopwatch();
sw.Start();

    var d = new ConcurrentDictionary < int,  int > ();
    for(int i = 0; i < 1000000; i++) d[i] = 123;
    for(int i = 1000000; i < 2000000; i++) d[i] = 123;
    for(int i = 2000000; i < 3000000; i++) d[i] = 123;
    Console.WriteLine("baseline = " + sw.Elapsed);

sw.Restart();

    var d2 = new Dictionary < int, int > ();
    for(int i = 0; i < 1000000; i++)         lock (d2) d2[i] = 123;
    for(int i = 1000000; i < 2000000; i++)   lock (d2) d2[i] = 123;
    for(int i = 2000000; i < 3000000; i++)   lock (d2) d2[i] = 123;
    Console.WriteLine("baseline = " + sw.Elapsed);

sw.Stop();

结果:(多次测试,值相同(+/-))。

baseline = 00:00:01.2604656
baseline = 00:00:00.3229741

问题:

什么使单个线程环境中的ConcurrentDictionary<,> 更多更慢?

我的第一直觉是lock(){}总是会慢一些。但显然不是。

8 个答案:

答案 0 :(得分:27)

好吧,ConcurrentDictionary允许的可能性它可以被多个线程使用。对我而言,这需要更多的内部管理而不是假设它可以逃脱,而不必担心来自多个线程的访问。如果反过来已经解决了,我会非常惊讶 - 如果更安全的版本总是更快,为什么你会使用安全性较低的版本?

答案 1 :(得分:24)

ConcurrentDictionary对于同一操作而言,Dictionary仅比{{1}}的开销更大的原因。如果你深入研究资料来源,这显然是正确的

  • 它使用锁定索引器
  • 它使用易失性写入
  • 必须对.Net
  • 中不保证是原子的值进行原子写入
  • 它在核心添加例程中有额外的分支(是否采取锁定,进行原子写入)

所有这些成本都会产生,无论其使用的线程数是多少。这些费用可能单独很小,但不是免费的,而且随着时间的推移会增加

答案 2 :(得分:3)

  

ConcurrentDictionary与Dictionary

     

一般情况下,使用a   System.Collections.Concurrent.ConcurrentDictionary in   您要添加和更新键或值的任何场景   同时来自多个线程。在涉及频繁的情况下   更新和相对较少的读取,ConcurrentDictionary通常提供适度的好处。在涉及的场景中   许多读取和许多更新,ConcurrentDictionary   通常在具有任意数量的计算机上明显更快   芯

     

在涉及频繁更新的方案中,您可以增加   ConcurrentDictionary和中的并发度   然后测量以查看计算机上的性能是否会提高   有更多核心。如果更改并发级别,请避免全局   尽可能多的操作。

     

如果您只是阅读键或值,则词典是   更快,因为如果不是字典则不需要同步   被任何线程修改。

链接:https://msdn.microsoft.com/en-us/library/dd997373%28v=vs.110%29.aspx

答案 3 :(得分:2)

如果在单个线程中完成所有操作,则在一个线程中使用ConcurrentDictionary或同步访问没有意义。当然字典会胜过ConcrurrentDictionary。

很大程度上取决于使用模式和线程数。这是一个测试,表明ConcurrentDictionary在线程数量增加的情况下胜过字典和锁。

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp
{

    class Program
    {

        static void Main(string[] args)
        {
            Run(1, 100000, 10);
            Run(10, 100000, 10);
            Run(100, 100000, 10);
            Run(1000, 100000, 10);
            Console.ReadKey();
        }

        static void Run(int threads, int count, int cycles)
        {
            Console.WriteLine("");
            Console.WriteLine($"Threads: {threads}, items: {count}, cycles:{cycles}");

            var semaphore = new SemaphoreSlim(0, threads);

            var concurrentDictionary = new ConcurrentDictionary<int, string>();

            for (int i = 0; i < threads; i++)
            {
                Thread t = new Thread(() => Run(concurrentDictionary, count, cycles,  semaphore));
                t.Start();
            }

            Thread.Sleep(1000);

            var w = Stopwatch.StartNew();

            semaphore.Release(threads);

            for (int i = 0; i < threads; i++)
                semaphore.Wait();

            Console.WriteLine($"ConcurrentDictionary: {w.Elapsed}");

            var dictionary = new Dictionary<int, string>();
            for (int i = 0; i < threads; i++)
            {
                Thread t = new Thread(() => Run(dictionary, count, cycles, semaphore));
                t.Start();
            }

            Thread.Sleep(1000);

            w.Restart();

            semaphore.Release(threads);


            for (int i = 0; i < threads; i++)
                semaphore.Wait();

            Console.WriteLine($"Dictionary: {w.Elapsed}");

        }

        static void Run(ConcurrentDictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
        {
            semaphore.Wait();
            try
            {
                for (int i = 0; i < cycles; i++)
                    for (int j = 0; j < elements; j++)
                    {
                        var x = dic.GetOrAdd(i, x => x.ToString());
                    }
            }
            finally
            {
                semaphore.Release();
            }
        }

        static void Run(Dictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
        {
            semaphore.Wait();
            try
            {
                for (int i = 0; i < cycles; i++)
                    for (int j = 0; j < elements; j++)
                        lock (dic)
                        {
                            if (!dic.TryGetValue(i, out string value))
                                dic[i] = i.ToString();
                        }
            }
            finally
            {
                semaphore.Release();
            }
        }
    }
}

线程:1,项目:100000,周期:10 并发词典:00:00:00.0000499 字典:00:00:00.0000137

线程:10,项目:100000,周期:10 并发词典:00:00:00.0497413 字典:00:00:00.2638265

线程:100,项目:100000,周期:10 并发词典:00:00:00.2408781 字典:00:00:02.2257736

线程:1000,项目:100000,周期:10 并发词典:00:00:01.8196668 字典:00:00:25.5717232

答案 4 :(得分:1)

ConcurrentDictionary<>在创建时创建一组内部锁定对象(这由concurrencyLevel决定,除其他因素外) - 这组锁定对象用于控制对内部存储桶结构的访问在一系列精细的锁中。

在单线程场景中,不需要锁,因此获取和释放这些锁的额外开销可能是您所看到的差异的根源。

答案 5 :(得分:0)

我刚刚在这里写了我的无锁线程安全写时复制字典实现:

http://www.singulink.com/CodeIndex/post/fastest-thread-safe-lock-free-dictionary

对于快速的写入和查找通常非常快速,通常以100%标准Dictionary的速度运行而不会锁定。如果您偶尔写作并且经常阅读,那么这是最快的选择。

由于我将其用于缓存,因此它目前仅可追加,但如果有流行的需求,我也可以在其上添加删除方法。

ConcurrentDictionary的速度很慢,因为它会在每次操作中取消读/写锁定。读/写锁甚至比普通锁慢,但是它允许多个读取器而不会阻塞。

我的实现通过在不对字典进行更新的情况下,在正常情况下消除了对任何读取锁定的需求,从而提供了最佳的读取性能。权衡是在应用更新后需要复制和交换字典(这是在后台线程上完成的),但是如果您不经常写或仅在初始化期间写一次,那么这种权衡绝对值得它。

答案 6 :(得分:0)

  

是什么使ConcurrentDictionary<,>在单线程环境中慢得多?

在多线程环境中使其更快所需的机器开销。

  

我的第一个直觉是lock(){}总是会变慢。但显然不是。

lock毫无争议,非常便宜。您可以每秒lock进行一百万次,并且只要您是从单个线程执行的,您的CPU甚至都不会注意到。导致争夺多线程程序性能的原因是争用锁。当多个线程为同一lock激烈竞争时,几乎所有线程都必须等待持有锁的幸运线程才能释放它。这就是ConcurrentDictionary及其精细锁定实现的亮点。并发性越强(处理器/核越多),它的作用就越大。

答案 7 :(得分:-5)

您的测试错误:您必须先停止秒表!

        Stopwatch sw = new Stopwatch();      
        sw.Start();
        var d = new ConcurrentDictionary<int, int>();
        for (int i = 0; i < 1000000; i++) d[i] = 123;
        for (int i = 1000000; i < 2000000; i++) d[i] = 123;
        for (int i = 2000000; i < 3000000; i++) d[i] = 123;
        sw.Stop();
        Console.WriteLine("baseline = " + sw.Elapsed);



        sw.Start();
        var d2 = new Dictionary<int, int>();
        for (int i = 0; i < 1000000; i++) lock (d2) d2[i] = 123;
        for (int i = 1000000; i < 2000000; i++) lock (d2) d2[i] = 123;
        for (int i = 2000000; i < 3000000; i++) lock (d2) d2[i] = 123;
        sw.Stop();
        Console.WriteLine("baseline = " + sw.Elapsed);

        sw.Stop();

- 输出: