使用 ConcurrentBag(Of MyType) 相对于仅使用 List(Of MyType) 有什么优势呢? MSDN页面上关于CB的内容指出
ConcurrentBag(Of T) 是一个线程安全的 袋(bag)实现,针对在同一线程中将生产和 消耗数据存储在袋中的情况进行了优化
那么这有什么优势呢?我能理解并发命名空间中其他集合类型的优势,但这个让我感到困惑。
使用 ConcurrentBag(Of MyType) 相对于仅使用 List(Of MyType) 有什么优势呢? MSDN页面上关于CB的内容指出
ConcurrentBag(Of T) 是一个线程安全的 袋(bag)实现,针对在同一线程中将生产和 消耗数据存储在袋中的情况进行了优化
那么这有什么优势呢?我能理解并发命名空间中其他集合类型的优势,但这个让我感到困惑。
ConcurrentBag内部使用了多个不同的列表(Lists),每个列表对应一个写线程。
所引用的那个语句的意思是,在从Bag中读取元素时,会优先选择和当前线程对应的列表。也就是说,在冒险去争取其他线程列表的元素之前,它将首先检查当前线程的列表是否有该元素。
这样可以在多个线程同时读写时最小化锁竞争。当读取线程没有列表或其列表为空时,它必须锁定分配给其他线程的列表。但是,如果您有多个线程都从自己的列表中读取和写入元素,则永远不会出现锁竞争。
ConcurrentBag<T>
可以安全地从多个线程访问,而List<T>
则不行。如果线程安全访问对您的情况很重要,那么像ConcurrentBag<T>
这样的类型可能比List<T>
+手动锁定更有优势。在我们真正回答这个问题之前,我们需要了解一些关于您场景的详细信息。List<T>
是一个有序集合,而ConcurrentBag<T>
则不是。简言之,我会说本地锁速度更快,但差异微不足道(或者我在设置测试时出了问题)。
性能分析:
private static IEnumerable<string> UseConcurrentBag(int count)
{
Func<string> getString = () => "42";
var list = new ConcurrentBag<string>();
Parallel.For(0, count, o => list.Add(getString()));
return list;
}
private static IEnumerable<string> UseLocalLock(int count)
{
Func<string> getString = () => "42";
var resultCollection = new List<string>();
object localLockObject = new object();
Parallel.For(0, count, () => new List<string>(), (word, state, localList) =>
{
localList.Add(getString());
return localList;
},
(finalResult) => { lock (localLockObject) resultCollection.AddRange(finalResult); }
);
return resultCollection;
}
private static void Test()
{
var s = string.Empty;
var start1 = DateTime.Now;
var list = UseConcurrentBag(5000000);
if (list != null)
{
var end1 = DateTime.Now;
s += " 1: " + end1.Subtract(start1);
}
var start2 = DateTime.Now;
var list1 = UseLocalLock(5000000);
if (list1 != null)
{
var end2 = DateTime.Now;
s += " 2: " + end2.Subtract(start2);
}
if (!s.Contains("sdfsd"))
{
}
}
使用ConcurrentBag在5M条记录中运行3次并对其进行误差处理
" 1: 00:00:00.4550455 2: 00:00:00.4090409"
" 1: 00:00:00.4190419 2: 00:00:00.4730473"
" 1: 00:00:00.4780478 2: 00:00:00.3870387"
使用Local lock在5M条记录上运行ConcurrentBag 3次:
" 1: 00:00:00.5070507 2: 00:00:00.3660366"
" 1: 00:00:00.4470447 2: 00:00:00.2470247"
" 1: 00:00:00.4420442 2: 00:00:00.2430243"
使用50M条记录:
" 1: 00:00:04.7354735 2: 00:00:04.7554755"
" 1: 00:00:04.2094209 2: 00:00:03.2413241"
我认为本地锁稍微更快一些。
更新: 在(Xeon X5650 @ 2.67GHz 64bit Win7 6 core)上,“本地锁”表现得更好。
使用50M条记录:
1: 00:00:09.7739773 2: 00:00:06.8076807
1: 00:00:08.8858885 2: 00:00:04.6184618
1: 00:00:12.5532552 2: 00:00:06.4866486
与其他并发集合不同,ConcurrentBag<T>
被优化为单线程使用。
与List<T>
不同,ConcurrentBag<T>
可以同时从多个线程中使用。