並發集合
1 為什麼使用並發集合?
原因主要有以下幾點:
System.Collections和System.Collections.Generic名稱空間中所提供的經典列表、集合和數組都不是安全執行緒的,若無同步機制,他們不適合於接受並發的指令來添加和刪除元素。
在並發代碼中使用上述經典集合需要複雜的同步管理,使用起來很不方便。
使用複雜的同步機制會大大降低效能。
NET Framework 4所提供的新的集合儘可能地減少需要使用鎖的次數。這些新的集合通過使用比較並交換(compare-and-swap,CAS)指令和記憶體屏障,避免使用互斥的重量級鎖。這對效能有保障。
注意:與經典集合相比,並發集合會有更大的開銷,因此在串列代碼中使用並發集合無意義,只會增加額外的開銷且運行速度比訪問經典集合慢。
2 並發集合
1)ConcurrentQueue:安全執行緒的先進先出 (FIFO) 集合
主要方法:
Enqueue(T item);將對象添加到集合結尾。
TryDequeue(out T result); 嘗試移除並返回位於集合開始處的對象,傳回值表示操作是否成功。
TryPeek(out T result);嘗試返回集合開始處的對象,但不將其移除,傳回值表示操作是否成功。
說明:
2)ConcurrentStack:安全執行緒的後進先出 (LIFO) 集合
主要方法及屬性:
Push(T item);將對象插入集合的頂部。
TryPop(out T result);嘗試彈出並返回集合頂部的對象,傳回值表示操作是否成功。
TryPeek(out T result);嘗試返回集合開始處的對象,但不將其移除,傳回值表示操作是否成功。
IsEmpty { get; }指示集合是否為空白。
PushRange(T[] items);將多個對象插入集合的頂部。
TryPopRange(T[] items);彈出頂部多個元素,返回結果為彈出元素個數。
說明:
與ConcurrentQueue相似地,ConcurrentStack完全無鎖的,但當CAS操作失敗且面臨資源爭用時,它可能會自旋並且重試操作。
擷取集合是否包含元素使用IsEmpty屬性,而不是通過判斷Count屬性是否大於零。調用Count比調用IsEmpty開銷大。
使用PushRange(T[] items)和TryPopRange(T[] items)時注意緩衝引起的額外開銷和額外的記憶體消耗。
3) ConcurrentBag:元素可重複的無序集合
主要方法及屬性:
TryPeek(out T result);嘗試從集合返回一個對象,但不移除該對象,傳回值表示是否成功獲得該對象。
TryTake(out T result);嘗試從集合返回一個對象並移除該對象,傳回值表示是否成功獲得該對象。
Add(T item);將對象添加到集合中。
IsEmpty { get; }解釋同ConcurrentStack
說明:
ConcurrentBag為每一個訪問集合的線程維護了一個本地隊列,在可能的情況下,它會以無鎖的方式訪問本地隊列。
ConcurrentBag在同一個線程添加和刪除元素的場合下效率非常高。
因為ConcurrentBag有時會需要鎖,在生產者線程和消費者線程完全分開的情境下效率非常低。
ConcurrentBag調用IsEmpty的開銷非常大,因為這需要臨時獲得這個無序組的所有鎖。
4)BlockingCollection:實現
System.Collections.Concurrent.IProducerConsumerCollection<T> 的安全執行緒集合,提供阻塞和限制功能
主要方法及屬性:
BlockingCollection(int boundedCapacity);boundedCapacity表示集合限制大小。
CompleteAdding();將BlockingCollection執行個體標記為不再接受任何添加。
IsCompleted { get; }此集合是否已標記為已完成添加並且為空白。
GetConsumingEnumerable();從集合中移除並返回移除的元素
Add(T item);添加元素到集合。
TryTake(T item, int millisecondsTimeout, CancellationToken cancellationToken);
說明:
使用BlockingCollection()建構函式執行個體化BlockingCollection,意味著不設定boundedCapacity,那麼boundedCapacity為預設值: int.MaxValue。
限界:使用BlockingCollection(int boundedCapacity),設定boundedCapacity的值,當集合容量達到這個值得時候,向BlockingCollection添加元素的線程將會被阻塞,直到有元素被刪除。
限界功能可控制記憶體中集合最大大小,這對於需要處理大量元素的時候非常有用。
預設情況下,BlockingCollection封裝了一個ConcurrentQueue。可以在建構函式中指定一個實現了IProducerConsumerCollection介面的並發集合,包括:ConcurrentStack、ConcurrentBag。
使用此集合包含易於無限制等待的風險,所以使用TryTake更加,因為TryTake提供了逾時控制,指定的時間內可以從集合中移除某個項,則為 true;否則為 false。
5)ConcurrentDictionary:可由多個線程同時訪問的索引值對的安全執行緒集合。
主要方法
AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory);如果指定的鍵尚不存在,則將鍵/值對添加到 字典中;如果指定的鍵已存在,則更新字典中的鍵/值對。
GetOrAdd(TKey key, TValue value);如果指定的鍵尚不存在,則將鍵/值對添加到字典中。
TryRemove(TKey key, out TValue value);嘗試從字典中移除並返回具有指定鍵的值。
TryUpdate(TKey key, TValue newValue, TValue comparisonValue);將指定鍵的現有值與指定值進行比較,如果相等,則用第三個值更新該鍵。
說明:
6)IProducerConsumerCollection:定義供生產者/消費者用來操作安全執行緒集合的方法。 此介面提供一個統一的表示(為生產者/消費者集合),從而更進階別抽象如 System.Collections.Concurrent.BlockingCollection<T>可以使用集合作為基礎的儲存機制。
3.常用模式
1)並行的生產者-消費者模式
定義:
產生者和消費者是此模式中的兩類物件模型,消費者依賴於生產者的結果,生產者產生結果的同時,消費者使用結果。
圖1 並行的生產者-消費者模式
說明:
並發集合用在此模式下非常合適,因為並發集合支援此模式中對象的並行操作。
若不使用並發集合,那麼就要加入同步機制,從而使程式變得比較複雜,難於維護和理解,同時大大降低效能。
為生產者消費者模式,縱軸為時間軸,產生者與消費者的並不在一條時間軸上,但二者有交叉,意在表明產生者先產生結果,而後消費者才真正使用了產生者產生的資料。
2)流水線模式
定義:
流水線由多個階段構成,每個階段由一系列的生產者和消費者構成。一般來講前一個階段是後一個階段的產生者;依靠相鄰兩個階段之間的緩衝區隊列,每個階段可以並發執行。
圖2 並行的流水線模式
說明:
4 使用方式
僅以ConcurrentBag和BlockingCollection為例,其他的並發集合與之相似。
ConcurrentBag
List<string> list = ......ConcurrentBag<string> bags = new ConcurrentBag<string>();Parallel.ForEach(list, (item) => { //對list中的每個元素進行處理然後,加入bags中 bags.Add(itemAfter);});
BlockingCollection—生產者消費者模式
public static void Execute(){ //調用Invoke,使得生產者任務和消費者任務並存執行 //Producer方法和Customer方法在Invoke中的參數順序任意,不論何種順序都會獲得正確的結果 Parallel.Invoke(()=>Customer(),()=>Producer()); Console.WriteLine(string.Join(",",customerColl));}//生產者集合private static BlockingCollection<int> producerColl = new BlockingCollection<int>(); //消費者集合private static BlockingCollection<string> customerColl = new BlockingCollection<string>();public static void Producer(){ //迴圈將資料加入產生者集合 for (int i = 0; i < 100; i++) { producerColl.Add(i); } //設定訊號,表明不在向生產者集合中加入新資料 //可以設定更加複雜的通知形式,比如資料量達到一定值且其中的資料滿足某一條件時就設定完成添加 producerColl.CompleteAdding();}public static void Customer(){ //調用IsCompleted方法,判斷生產者集合是否在添加資料,是否還有未"消費"的資料 //注意不要使用IsAddingCompleted,IsAddingCompleted只表明集合標記為已完成添加,而不能說明其為空白 //而IsCompleted為ture時,那麼IsAddingCompleted為ture且集合為空白 while (!producerColl.IsCompleted) { //調用Take或TryTake "消費"資料,消費一個,移除一個 //TryAdd的好處是提供逾時機制 customerColl.Add(string.Format("消費:{0}", producerColl.Take())); }}
以上就是.NET多線程編程—並發集合的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!