1.對非線程安全類List的一些總結(首先了解)
描述場景:一個項目的一個功能點,需要從接口接受返回數據,並對返回的數據進行一些業務處理,處理完成之後,添加到一個List
每次從接口中取回的數據量不等,最多會有上百條。雖說上百條也不算多,但是每條數據都要經過一系列的業務處理,感覺這樣也挺耗時的,於是考慮使用Parallel.Foreach來進行並行處理。
項目完成之後,對比了一下並行和非並行的情況,發現並行之後並沒有提高多少效能,倒是遇到了一些比較怪異的問題。
出現的問題:Parallel.Foreach 中對List
分析:因爲List
從源碼中我們可以看到List
我們把Add方法和擴容方法摘抄如下:
public void Add(T item) {
if (_size == _items.Length) EnsureCapacity(_size + 1);
_items[_size++] = item;
_version++;
}
private void EnsureCapacity(int min) {
if (_items.Length < min) {
int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}
瞭解了List
1、 List
導致這個問題的原因其實還是挺明顯的。當兩個線程(ThreadA和TreadB),同時調用Add方法添加不同的值的時候,如果此時ThreadA和ThreadB獲取到的size相同,就會出現下面這種情況:
ThreadA:List
ThreadB:List
這種情況下,在size這個位置只會有一個ThreadB設置的值,ThreadA設置的值將會被替換掉,這也就是造成Item數量比預期少的原因。
2、 List
其實和上面類似,看Add中的代碼:
_items[_size++] = item;
我們改變一下,變成:
(1)_size = _size+1;
(2)Items[_size] = item;
如果ThreadA執行完(1)之後ThreadB獲取到新的_size也執行了(1)那此時_size就相當於是加2了,所以_size+1索引位置的項就是T的默認值了(值類型會值類型的默認值,引用類型爲null)。這樣就能解釋爲什麼會出現null的原因了。
解決方案:其實這兩個問題完全就是同一個問題,只不過表象不同而已。最終解決方案很簡單,要麼自己加鎖,要麼使用線程安全的ConcurrentBag
1.手寫一個類繼承自List
myList.Add(object1);
//修改爲
public static object lockData=new object();
lock(lockData)
{
myList.Add(object1)
}
2.當排序並不重要時,包可用於存儲對象,而與集不同,包支持重複項。 ConcurrentBag
ConcurrentBag
ConcurrentBag