ConcurrentBag与自定义线程安全列表

时间:2015-05-29 13:38:52

标签: c# multithreading concurrency .net-4.5 readerwriterlockslim

我有一个.NET 4.5单实例WCF服务,它维护列表中的项目集合,这些项目将同时具有并发读者和编写者,但读者的数量远远超过作者。

我目前正在决定是使用BCL ConcurrentBag<T>还是使用我自己的自定义通用ThreadSafeList类(扩展IList<T>并封装BCL ReaderWriterLockSlim,因为这是更适合多个并发读者。)

通过模拟1m读取器的并发场景(简单地运行Sum Linq查询)和仅100个编写器(向列表添加项目)来测试这些实现时,我发现了许多性能差异。

对于我的性能测试,我有一个任务列表:

List<Task> tasks = new List<Task>();

测试1:如果我使用以下代码创建1m读者任务,然后创建100个编写器任务:

tasks.AddRange(Enumerable.Range(0, 1000000).Select(n => new Task(() => { temp.Where(t => t < 1000).Sum(); })).ToArray());
tasks.AddRange(Enumerable.Range(0, 100).Select(n => new Task(() => { temp.Add(n); })).ToArray());

我得到以下时间结果:

  • ConcurrentBag:~300ms
  • ThreadSafeList:~520ms

测试2:但是,如果我创建1m阅读器任务与100个编写器任务混合(其中要执行的任务列表可以是{Reader,Reader,Writer,Reader,Reader,Writer等}

foreach (var item in Enumerable.Range(0, 1000000))
{
    tasks.Add(new Task(() => temp.Where(t => t < 1000).Sum()));
    if (item % 10000 == 0)
        tasks.Add(new Task(() => temp.Add(item)));
}

我得到以下时间结果:

  • ConcurrentBag:~4000ms
  • ThreadSafeList:~800ms

我获取每个测试执行时间的代码如下:

Stopwatch watch = new Stopwatch();
watch.Start();
tasks.ForEach(task => task.Start());
Task.WaitAll(tasks.ToArray());
watch.Stop();
Console.WriteLine("Time: {0}ms", watch.Elapsed.TotalMilliseconds);

测试1中ConcurrentBag的效率更好,测试2中ConcurrentBag的效率比我的自定义实现更差,因此我发现很难决定使用哪一个。

Q1。当我改变的唯一事情是列表中任务的排序时,为什么结果如此不同?

Q2。有没有更好的方法来改变我的测试以使其更公平?

1 个答案:

答案 0 :(得分:1)

  

当我改变的唯一事情是,为什么结果如此不同   列表中任务的顺序?

我最好的猜测是Test #1实际上并没有读取项目,因为没有什么可读的。任务执行的顺序是:

  1. 从共享池中读取1M次并计算总和
  2. 100次写入共享池
  3. 你的Test # 2混合了读写,这就是为什么,我猜,你会得到不同的结果。

      

    有没有更好的方法来改变我的测试以使其更公平?

    在开始任务之前,请尝试随机化任务顺序。可能很难重现相同的结果,但您可能更接近现实世界的使用。

    最终,您的问题是乐观并发(Concurrent*类)与悲观并发(基于锁定)的区别。根据经验,当您同时访问共享资源的机会较低时,更喜欢乐观并发。当同时访问的机会高时,更喜欢悲观的。