當同個時間點,生產者一邊在產生資源,並將其填入 Queue ;另一邊,消費者從 Queue 中取出資源,以利後續處理。
一來一往之處,如果沒有同步的機制,極有可能造成存取的錯亂,嚴重影響資料的正確性。
首先上場的是 .Net Lock
機制,利用指定指定物件的互斥鎖定,以確保鎖定期間,鎖定範圍內的陳述式不受其他執行緒影響,正常的完成任務,直到離開鎖定範圍。
在上面的示意圖中,兩名生產者與一名消費者,先後存取 Queue,在 Lock 機制的保護下,Queue 會依據請求時間的先後,依序完成 producer 1
、Customer 1
、 Producer 2
的存取動作。
在 lock
的使用上,MSDN 之中,也特別提到同步處理執行緒對共用資源的存取時,請鎖定專用物件執行個體。
也就是用來用 lock
的指定物件,盡可能使用專門的物件。
private readonly object _lock = new object ();
lock(_lock)
{
// 要確保資料正確性旳程式區塊
}
同時為避免死鎖(deadlock) 或鎖定爭用(lock contention) 的情況發生,請避免...
this
、Type 類別
與字串
。如果有興趣進一步了解,可以進一步閱讀 說說lock到底鎖誰? 這篇文章。
接著,我們繼承前面實作的 Queue
,將其加上 Lock
機制。
public class LockQueue<T> : Queue<T>
{
private readonly object _lock = new object ();
public override void Enqueue (T item)
{
lock (_lock) { base.Enqueue (item); }
}
public override bool IsEmpty => { lock (_lock) { base.IsEmpty; } }
public T Dequeue ()
{
lock (_lock) { return base.Dequeue (); }
}
}
測試程式如下:
private static void Main(string[] args)
{
var queue = new Queue<int>();
Random rand = new Random();
var timesLimit = 10;
var producer = Task.Run(() =>
{
var times = timesLimit;
while (times-- != 0)
{
var item = rand.Next(500);
System.Console.WriteLine($"Enqueue: {item}");
queue.Enqueue(item);
Thread.Sleep(100);
}
System.Console.WriteLine("produce end.");
});
var consumer1 = Task.Run(() => { Program.Consumer(queue); });
var consumer2 = Task.Run(() => { Program.Consumer(queue); });
System.Console.Read();
}
private static void Consumer<T>(Queue<T> queue)
{
while (true)
{
if (queue.IsEmpty)
{
Thread.Sleep(10);
continue;
}
var item = queue.Dequeue();
System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Dequeue: {item}");
Thread.Sleep(100);
}
}