.Net 執行緒安全集合

尋找無名的特質發表於2022-01-10

.Net 提供了基於生產-消費模式的集合類,這些集合對多執行緒訪問安全,定義在System.Collections.Concurrent名稱空間中。這個名稱空間中包括基礎介面IProduceConsumerCollection,這個介面定義了執行緒安全集合的基本操作。這個名稱空間中還包括常用的集合:

  • BlockingCollection
  • ConcurrentBag
  • ConcurentDictionary<TKey,TValue>
  • ConcurrentQueue
  • ConcurentStack

在使用生產-消費模式時,我們經常使用兩個執行緒,在一個執行緒向集合新增資料,在另一個執行緒從集合中提取資料進行處理。我們可以使用實現IProduceConsumerCollection介面的集合,比如ConcurrentQueue等等。通常將從集合取資料的程式碼放在一個無盡迴圈中,如果集合中沒有資料就繼續迴圈。很多情況下,我們希望如果集合中沒有資料,這個執行緒阻塞等待,直到有資料時再繼續。這時我們可以使用BlockingCollection,這個集合提供了Add(新增資料)和Take(阻塞獲取資料)方法。

下面是BlockingCollection的示例。這個集合類的Take方法可以從集合中獲取並去掉一個物件,當集合為空時,可以使執行緒處於阻塞狀態。

Console.WriteLine("--------------------------------");
Console.WriteLine("測試一個執行緒向集合新增資料,另一個執行緒讀取資料,請輸入人名,輸入exit退出");
BlockingCollection<string> names=new BlockingCollection<string>();

Task.Run(() =>
{
    while (true)
    {
        var name = names.Take();
        Console.WriteLine("你好,"+name);
    }

});

var name = Console.ReadLine();
while (name!="exit")
{
    if(!string.IsNullOrEmpty(name))   names.Add(name);
    name = Console.ReadLine();
}

BlockingCollection的另一個功能是可以封裝其它的IProduceConsumerCollection集合,實現不同的新增和獲取順序,比如,如果在建構函式中傳入ConcurrentQueue,新增和獲取就與佇列相同——“先進先出”,如果傳入ConcurrentStack,順序就與堆疊相同——“先進後出”,下面是示例程式碼:

using System.Collections.Concurrent;

Console.WriteLine("--------------------------------");
Console.WriteLine("測試BlockingCollection 和 ConcurrentQueue");

var queue = new ConcurrentQueue<string>();
var blockqueue= new BlockingCollection<string>(queue, 100);

Console.WriteLine("加入name1");
blockqueue.Add("name1");
Console.WriteLine("加入name2");
blockqueue.Add("name2");
Console.WriteLine("加入name3");
blockqueue.Add("name3");

Console.WriteLine(blockqueue.Take());
Console.WriteLine(blockqueue.Take());
Console.WriteLine(blockqueue.Take());

Console.WriteLine("--------------------------------");
Console.WriteLine("測試BlockingCollection 和 ConcurrentStack");

var cq = new ConcurrentStack<string>();
var bc = new BlockingCollection<string>(cq, 100);

Console.WriteLine("加入name1");
bc.Add("name1");
Console.WriteLine("加入name2");
bc.Add("name2");
Console.WriteLine("加入name3");
bc.Add("name3");

Console.WriteLine(bc.Take());
Console.WriteLine(bc.Take());
Console.WriteLine(bc.Take());

ConcurrentBag需要特別說明一下,在“純生產-消費”場景中(一個執行緒要麼向集合新增專案,要麼從集合中獲取專案,但不能即新增又獲取),ConcurrentBag效能要比其他型別的集合慢,但在“混合生產-消費”場景中(一個執行緒即可以向集合新增專案,也可以獲取專案),ConcurrentBag的效能要比其它型別的集合快。

相關文章