线程安全,(几乎)无锁,快速数据结构用于重写,然后重读

时间:2014-01-05 12:58:03

标签: c# multithreading dictionary concurrency

我正在运行一些基于.NET线程安全和非线程安全字典的实验,以及我的自定义实验。

每个写入20,000,000(2000万)英镑的结果如下:

  1. 非线程安全:909毫秒(少于1秒)词典
  2. 线程安全:11914毫秒(超过11秒) ConcurrentDictionary
  3. 自定义:909毫秒(不到1秒) 2字典
  4. 线程安全(ConcurrentTryAdd):12697毫秒(超过12秒)不比#2更好
  5. 这些测试是在单线程环境中进行的,我试图获得非线程安全字典的速度,并确保线程的安全性。

    到目前为止结果很有希望,我很惊讶ConcurrentDictionary的处理能力有多差,可能仅适用于某些场景?

    无论如何,下面是我用来测试三个字典的代码,你能告诉我我的自定义代码是否是线程安全的吗?我是否必须为 if(_list.ContainsKey(threadId))添加锁定?我不这么认为,因为它只是一个读取,并且当字典中添加了一个元素(写入)时,它被一个锁保护,阻止其他线程试图读取它。

    一旦线程拥有字典就没有锁,因为另一个线程无法写入同一个字典,因为每个线程都有自己的字典(基于ManagedThreadId),使其像单个线程一样安全。

      

    主要

    using System;
    using System.Diagnostics;
    
    namespace LockFreeTests
    {
        class Program
        {
            static void Main(string[] args)
            {
                var sw = Stopwatch.StartNew();
                int i = 20000000;  // 20 million
    
                IWork work = new Custom(); // Replace with: Control(), Concurrent(), or Custom()
                work.Start(i);
    
                sw.Stop();
                Console.WriteLine("Total time: {0}\r\nPress anykey to continue...", sw.Elapsed.TotalMilliseconds);
                Console.ReadKey(true);
            }
        }
    }
    
      

    非线程安全

    using System.Collections.Generic;
    
    namespace LockFreeTests
    {
        class Control : IWork
        {
            public void Start(int i)
            {
                var list = new Dictionary<int, int>();
                for (int n = 0; n < i; n++)
                {
                    list.Add(n, n);
                }
            }
        }
    }
    
      

    线程安全

    using System.Collections.Concurrent;
    
    namespace LockFreeTests
    {
        class Concurrent : IWork
        {
            public void Start(int i)
            {
                var list = new ConcurrentDictionary<int, int>();
                for (int n = 0; n < i; n++)
                {
                    list.AddOrUpdate(n, n, (a, b) => b);
                }
            }
        }
    }
    
      

    线程安全(尝试添加)

    using System.Collections.Concurrent;
    
    namespace LockFreeTests
    {
        class ConcurrentTryAdd : IWork
        {
            public void Start(int i)
            {
                var list = new ConcurrentDictionary<int, int>();
                for (int n = 0; n < i; n++)
                {
                    bool result = list.TryAdd(n, n);
                    if (!result)
                    {
                        n--;
                    }
                }
            }
        }
    }
    
      

    定制

    using System.Collections.Generic;
    using System.Threading;
    
    namespace LockFreeTests
    {
        class Custom : IWork
        {
            private static Dictionary<int, Dictionary<int, int>> _list = null;
    
            static Custom()
            {
                _list = new Dictionary<int, Dictionary<int, int>>();
            }
    
            public void Start(int i)
            {
                int threadId = Thread.CurrentThread.ManagedThreadId;
    
                Dictionary<int, int> threadList = null;
                bool firstTime = false;
    
                lock (_list)
                {
                    if (_list.ContainsKey(threadId))
                    {
                         threadList = _list[threadId];
                    }
                    else
                    {
                        threadList = new Dictionary<int, int>();
                        firstTime = true;
                     }
                }
    
                for (int n = 0; n < i; n++)
                {
                    threadList.Add(n, n);
                }
    
                if (firstTime)
                {
                    lock (_list)
                    {
                        _list.Add(threadId, threadList);
                    }
                }
    
            }
        }
    }
    
      

    IWORK

    namespace LockFreeTests
    {
        public interface IWork
        {
            void Start(int i);
        }
    }
    

    多线程示例

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace LockFreeTests
    {
        class Program
        {
            static void Main(string[] args)
            {
                var sw = Stopwatch.StartNew();
                int totalWork = 20000000;  // 20 million
                int cores = Environment.ProcessorCount;
                int workPerCore = totalWork / cores;
    
                IWork work = new Custom(); // Replace with: Control(), Concurrent(), ConcurrentTryAdd(), or Custom()
                var tasks = new Task[cores];
    
                for (int n = 0; n < cores; n++)
                {
                    tasks[n] = Task.Factory.StartNew(() =>
                        {
                            work.Start(workPerCore);
                        });
                }
    
                Task.WaitAll(tasks);
                sw.Stop();
                Console.WriteLine("Total time: {0}\r\nPress anykey to continue...", sw.Elapsed.TotalMilliseconds);
                Console.ReadKey(true);
            }
        }
    }
    

    上面的代码运行528毫秒,速度提高了40%(来自单线程测试)

2 个答案:

答案 0 :(得分:0)

这不是线程安全的。

  

我是否必须为if(_list.ContainsKey(threadId))添加锁定?我不这么认为,因为它只是一个读取,并且当字典中添加了一个元素(写入)时,它被一个锁保护,阻止其他线程试图读取它。

是的,你需要在这里锁定以使其成为线程安全的。

答案 1 :(得分:0)

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

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

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