我有一个 .NET 4.5 单实例 WCF 服务,它维护一个列表中的项目集合,该列表将同时具有并发的读取器和写入器,但读取器比写入器多得多。
我目前正在决定是使用 BCLConcurrentBag<T>
还是使用我自己的自定义通用ThreadSafeList类(它扩展IList<T>
和封装了 BCL ReaderWriterLockSlim
,因为它更适合多个并发读取器)。
通过模拟 100 万个读者(简单地运行 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());
我得到以下计时结果:
- 并发包:~300ms
- 线程安全列表:~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)));
}
我得到以下计时结果:
- 并发包:~4000ms
- 线程安全列表:~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。有没有更好的方法来更改我的测试以使其更公平?