ConcurrentBag vs自定义线程安全列表

本文关键字:安全 列表 线程 自定义 vs ConcurrentBag | 更新日期: 2023-09-27 17:51:04

我有一个。net 4.5单实例WCF服务,它维护一个列表中的项目集合,该列表将同时具有并发的读和写器,但是读器比写器多得多。

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

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

对于我的性能测试,我有一个任务列表:
List<Task> tasks = new List<Task>();

测试1:如果我使用以下代码创建了1m个reader任务和100个writer任务:

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: ~ 300 ms
  • ThreadSafeList: ~ 520 ms

测试2:但是,如果我创建了1m个reader任务和100个writer任务(其中要执行的任务列表可以是{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: ~ 4000 ms
  • ThreadSafeList: ~ 800 ms

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

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。有没有更好的方法来改变我的考试,让它更公平?

ConcurrentBag vs自定义线程安全列表

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

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

  1. 从共享池中读取1M次并计算sum
  2. 向共享池写入100次

你的Test # 2混合了读和写,这就是为什么,我猜,你会得到不同的结果。

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

在开始任务之前,尝试随机化任务的顺序。复制相同的结果可能很困难,但您可以更接近真实世界的用法。

最后,你的问题是关于乐观并发(Concurrent*类)与悲观并发(基于锁)的区别。根据经验,当同时访问共享资源的可能性较低时,更倾向于乐观并发。当同时访问的机会很高时,更喜欢悲观的。