ConcurrentBag 与自定义线程安全列表
ConcurrentBag vs Custom Thread Safe List
我有一个 .NET 4.5 单实例 WCF 服务,它维护一个列表中的项目集合,该列表将同时并发 readers 和编写器,但 readers 比编写器多得多.
我目前正在决定是使用 BCL ConcurrentBag<T>
还是使用我自己的自定义泛型 ThreadSafeList class(扩展 IList<T>
并封装 BCL ReaderWriterLockSlim
因为这更适合多个并发 readers).
通过模拟 1m readers 的并发场景(简单地 运行 Sum Linq 查询)和只有 100 个编写器(将项目添加到列表中)来测试这些实现时,我发现了许多性能差异).
对于我的性能测试,我有一个任务列表:
List<Task> tasks = new List<Task>();
测试 1:如果我创建 1m reader 个任务,然后使用以下代码创建 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());
我得到以下计时结果:
- 并发包:~300 毫秒
- 线程安全列表:~520 毫秒
测试 2:但是,如果我创建 1m reader 任务与 100 个编写器任务混合(因此要执行的任务列表可以是 {Reader,Reader,Writer ,Reader,Reader,作家等}
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)));
}
我得到以下计时结果:
- 并发包:~4000ms
- 线程安全列表:~800 毫秒
我获取每次测试执行时间的代码如下:
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);
ConcurrentBag 在测试 1 中的效率更好,ConcurrentBag 在测试 2 中的效率比我的自定义实现差,因此我发现很难决定使用哪个。
Q1。当我唯一改变的是列表中任务的排序时,为什么结果如此不同?
Q2。有没有更好的方法来改变我的测试以使其更公平?
Why are the results so different when the only thing I’m changing is
the ordering of the tasks within the list?
我最好的猜测是 Test #1
实际上没有 阅读 项目,因为没有什么可读的。任务执行顺序为:
- 从shared pool读取1M次并计算sum
- 写入共享池 100 次
您的 Test # 2
混合读取和写入,这就是为什么,我猜,您会得到不同的结果。
Is there a better way to change my test to make it more fair?
开始任务之前,请尝试随机化任务顺序。可能很难重现相同的结果,但您可能会更接近真实世界的用法。
最终,您的问题是关于乐观并发 (Concurrent*
类) 与悲观并发(基于锁)的区别。根据经验,当您同时访问共享资源的机会很低时,更喜欢乐观并发。当同时访问的机会很高时,更喜欢悲观的。
我有一个 .NET 4.5 单实例 WCF 服务,它维护一个列表中的项目集合,该列表将同时并发 readers 和编写器,但 readers 比编写器多得多.
我目前正在决定是使用 BCL ConcurrentBag<T>
还是使用我自己的自定义泛型 ThreadSafeList class(扩展 IList<T>
并封装 BCL ReaderWriterLockSlim
因为这更适合多个并发 readers).
通过模拟 1m readers 的并发场景(简单地 运行 Sum Linq 查询)和只有 100 个编写器(将项目添加到列表中)来测试这些实现时,我发现了许多性能差异).
对于我的性能测试,我有一个任务列表:
List<Task> tasks = new List<Task>();
测试 1:如果我创建 1m reader 个任务,然后使用以下代码创建 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());
我得到以下计时结果:
- 并发包:~300 毫秒
- 线程安全列表:~520 毫秒
测试 2:但是,如果我创建 1m reader 任务与 100 个编写器任务混合(因此要执行的任务列表可以是 {Reader,Reader,Writer ,Reader,Reader,作家等}
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)));
}
我得到以下计时结果:
- 并发包:~4000ms
- 线程安全列表:~800 毫秒
我获取每次测试执行时间的代码如下:
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);
ConcurrentBag 在测试 1 中的效率更好,ConcurrentBag 在测试 2 中的效率比我的自定义实现差,因此我发现很难决定使用哪个。
Q1。当我唯一改变的是列表中任务的排序时,为什么结果如此不同?
Q2。有没有更好的方法来改变我的测试以使其更公平?
Why are the results so different when the only thing I’m changing is the ordering of the tasks within the list?
我最好的猜测是 Test #1
实际上没有 阅读 项目,因为没有什么可读的。任务执行顺序为:
- 从shared pool读取1M次并计算sum
- 写入共享池 100 次
您的 Test # 2
混合读取和写入,这就是为什么,我猜,您会得到不同的结果。
Is there a better way to change my test to make it more fair?
开始任务之前,请尝试随机化任务顺序。可能很难重现相同的结果,但您可能会更接近真实世界的用法。
最终,您的问题是关于乐观并发 (Concurrent*
类) 与悲观并发(基于锁)的区别。根据经验,当您同时访问共享资源的机会很低时,更喜欢乐观并发。当同时访问的机会很高时,更喜欢悲观的。