天天看點

C#并發處理-鎖OR線程安全?

每次寫部落格,第一句話都是這樣的:程式員很苦逼,除了會寫程式,還得會寫部落格!

當然,題外話說多了,咱進入正題!

背景

基于任務的程式設計、指令式資料并行和任務并行都要求能夠支援并發更新的數組、清單和集合。

在.NET Framework 4 以前,為了讓共享的數組、清單和集合能夠被多個線程更新,需要添加複雜的代碼來同步這些更新操作。

如您需要編寫一個并行循環,這個循環以無序的方式向一個共享集合中添加元素,那麼必須加入一個同步機制來保證這是一個線程安全的集合。

System.Collenctions和System.Collenctions.Generic 名稱空間中所提供的經典清單、集合和數組的線程都不是安全的,不能接受并發請求,是以需要對相應的操作方法執行串行化。

下面看代碼,代碼中并沒有實作線程安全和串行化:

C#并發處理-鎖OR線程安全?

View Code

代碼中開啟了三個并發操作,每個操作都向集合中添加1000條資料,在沒有保障線程安全和串行化的運作下,實際得到的資料并沒有3000條,結果如下:

為此我們需要采用Lock關鍵字,來確定每次隻有一個線程來通路  _Products.Add(product); 這個方法,代碼如下:

C#并發處理-鎖OR線程安全?

但是鎖的引入,帶來了一定的開銷和性能的損耗,并降低了程式的擴充性,在并發程式設計中顯然不适用。

System.Collections.Concurrent

.NET Framework 4提供了新的線程安全和擴充的并發集合,它們能夠解決潛在的死鎖問題和競争條件問題,是以在很多複雜的情形下它們能夠使得并行代碼更容易編寫,這些集合盡可能減少需要使用鎖的次數,進而使得在大部分情形下能夠優化為最佳性能,不會産生不必要的同步開銷。

需要注意的是:

線程安全并不是沒有代價的,比起System.Collenctions和System.Collenctions.Generic命名空間中的清單、集合和數組來說,并發集合會有更大的開銷。是以,應該隻在需要從多個任務中并發通路集合的時候才使用并發幾個,在串行代碼中使用并發集合是沒有意義的,因為它們會增加無謂的開銷。

為此,在.NET Framework中提供了System.Collections.Concurrent新的命名空間可以通路用于解決線程安全問題,通過這個命名空間能通路以下為并發做好了準備的集合。

1.BlockingCollection 與經典的阻塞隊列資料結構類似,能夠适用于多個任務添加和删除資料,提供阻塞和限界能力。

2.ConcurrentBag 提供對象的線程安全的無序集合

3.ConcurrentDictionary  提供可有多個線程同時通路的鍵值對的線程安全集合

4.ConcurrentQueue   提供線程安全的先進先出集合

5.ConcurrentStack   提供線程安全的後進先出集合

這些集合通過使用比較并交換和記憶體屏障等技術,避免使用典型的互斥重量級的鎖,進而保證線程安全和性能。

ConcurrentQueue 

ConcurrentQueue 是完全無鎖的,能夠支援并發的添加元素,先進先出。下面貼代碼,詳解見注釋:

C#并發處理-鎖OR線程安全?