C# ConcurrentBag与自定义线程安全列表
我有一个.NET4.5单实例WCF服务,它在一个列表中维护一个项目集合,该列表将同时具有读卡器和写卡器,但读卡器远远多于写卡器 我目前正在决定是使用BCLC# ConcurrentBag与自定义线程安全列表,c#,multithreading,concurrency,.net-4.5,readerwriterlockslim,C#,Multithreading,Concurrency,.net 4.5,Readerwriterlockslim,我有一个.NET4.5单实例WCF服务,它在一个列表中维护一个项目集合,该列表将同时具有读卡器和写卡器,但读卡器远远多于写卡器 我目前正在决定是使用BCLConcurrentBag还是使用我自己的自定义泛型类(它扩展了IList并封装了BCLReaderWriterLockSlim,因为它更适合多个并发读卡器) 在测试这些实现时,我通过模拟一个并发场景(仅运行一个Sum Linq查询)和100个writer(向列表中添加项),发现了许多性能差异 对于我的性能测试,我有一个任务列表: List&l
ConcurrentBag
还是使用我自己的自定义泛型类(它扩展了IList
并封装了BCLReaderWriterLockSlim
,因为它更适合多个并发读卡器)
在测试这些实现时,我通过模拟一个并发场景(仅运行一个Sum Linq查询)和100个writer(向列表中添加项),发现了许多性能差异
对于我的性能测试,我有一个任务列表:
List<Task> tasks = new List<Task>();
测试1中ConcurrentBag的效率比我的定制实现好,而测试2中ConcurrentBag的效率比我的定制实现差,因此我发现很难决定使用哪一个
问题1。当我唯一要改变的是列表中任务的顺序时,为什么结果会如此不同
问题2。有没有更好的办法来改变我的考试,使它更公平
为什么结果如此不同,而我唯一要改变的是
列表中任务的顺序
我最好的猜测是,Test#1
实际上并不读取项目,因为没有什么可读取的。任务执行的顺序为:
测试#2
混合了读写,这就是为什么我猜你会得到不同的结果
有没有更好的办法来改变我的考试,使它更公平
在开始任务之前,请尝试将任务的顺序随机化。可能很难重现相同的结果,但您可能会更接近真实世界的使用情况
最后,您的问题是关于乐观并发(
Concurrent*
classes)和悲观并发(基于锁)的区别。根据经验,当您同时访问共享资源的机会较低时,您更喜欢乐观并发。当同时访问的机会较高时,选择悲观的 谢谢你的意见!在测试1中,更改顺序使写入优先,读取第二,这会在性能上产生巨大的差异。ConcurrentBag将在9秒左右推出,而我的定制ThreadSafeList是1300ms。我相信测试2是一个很好的尝试,可以将任务的顺序随机化?我认为,由于WCF服务是一个发布-订阅服务器,订阅(写入列表)很少,但多条消息的并发广播(从列表中读取)很频繁,因此很有可能出现悲观的并发情况。我会使用Fisher-Yates Durstenfeld shuffle之类的方法。见实施。当您启动任务时,它可能看起来像:tasks.Shuffle().ForEach(task=>task.start())
看起来您的实现更好(但仅针对您的特定用例),ConcurrentBag针对写入包中的线程和从包中读取的线程进行了优化。如果您没有从包中读取相同的线程,请切换到ConcurrentQueue。阅读,在您的示例中,您使用的是“纯生产者-消费者场景”,每个线程要么只读取,要么只写入包,没有一个线程在同一个线程中读写。我同意-这让我问你什么时候会使用ConcurrentBag,因为根据我的理解,你不能在同一个线程上同时读写……你能吗?因此,我会让每个线程维护自己的列表。当然,将包命名为并发集合的一部分是个坏主意,因为它在多线程场景中效率低下?…除非我遗漏了什么!Bag适用于这样的情况:您有一群工作线程,它们将工作全部转储到一个池中,然后在完成后从同一个池中拉出。如果一个工作线程最终完成了他输入的所有工作(它在内部存储数据的方式将返回线程首先输入的数据),他将开始从其他线程“窃取”工作。或者另一个常见用途可能是工作线程尝试从池中提取,发现没有项目,创建新项目,然后,处理完资源后,添加新项/将旧项返回到池中。下一次同一线程需要池中的项目时,速度会更快,但是如果另一个线程访问池,它可以使用第一个threads对象,而无需创建自己的对象。关于这种行为的一个很好的现实例子是。我认为,由于你的广泛回答,这有理由提出自己的问题。我认为记住这一点的最好方法是连接池示例。
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());
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)));
}
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);