面試(對,最近在找作業面試...)被問到,.net 并發控制怎么做,BlockingQueue和CuncurrenceQueue有什么區別?
那么我們就來聊一下.net并發相關的知識,接下來會說到下面幾個并發控制相關功能:
lock
Monitor
Semaphore
CuncurrenceQueue
BlockingQueue
BlockingCollection
一、lock
說到并發控制,我們首先想到的肯定是 lock關鍵字,
這里要說一下,lock鎖的究竟是什么?是lock下面的代碼塊嗎,不,是locker物件,
我們想象一下,locker物件相當于一把門鎖(或者鑰匙),后面代碼塊相當于屋里的資源,
哪個執行緒先控制這把鎖,就有權訪問代碼塊,訪問完成后再釋放權限,下一個執行緒再進行訪問,
注意:如果代碼塊中的邏輯執行時間很長,那么其他執行緒也會一直等下去,直到上一個執行緒執行完畢,釋放鎖,
1 object locker = new object(); 2 3 private void Add() 4 { 5 lock (locker) 6 { 7 Thread.Sleep(1000); 8 counter++; 9 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Add counter={counter}."); 10 } 11 }
二、Moniter
Monitor是一個靜態類(System.Threading.Monitor),功能與lock關鍵字基本一樣,也是加鎖,控制并發,
有兩個重要的方法:
Monitor.Enter() //獲取一個鎖
Monitor.Exit() //釋放一個鎖
另外幾個方法:
public static bool TryEnter(object obj, int millisecondsTimeout) //相比于 public static void Enter(object obj) 方法,多了超時時間設定,如果等待超過一定時間,就不再等待了,另外,只有TryEnter()回傳值為true時,才能進入代碼塊,
public static bool Wait(object obj, int millisecondsTimeout) //這個方法在已經獲得鎖權限的代碼塊中呼叫時,或暫時釋放鎖,等待一定時間后,重新獲取鎖權限,繼續執行Wait后面的代碼,(真想不明怎么會有這種相互禮讓的操作)
public static void Pulse(object obj) //這個方法的解釋是,通知在等待佇列中的執行緒,鎖物件狀態改變,(測驗發現,此方法并不會真正改變鎖定狀態,只是通知的作用)
TryEnter代碼示例:
1 int counter = 0; 2 object locker = new object(); 3 4 private void Minus() 5 { 6 //加上try -catch-finally,防止由于例外,鎖無法釋放,這也是為什么我們更多使用lock而不是Moniter的原因, 7 try 8 { 9 //只有TryEnter()回傳值為true時,才能進入代碼塊,與Enter()方法不一樣 10 if (Monitor.TryEnter(locker, 5000)) 11 { 12 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Minus in"); 13 Thread.Sleep(1000); 14 counter--; 15 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Minus counter={counter}."); 16 } 17 } 18 catch (Exception ex) 19 { 20 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Minus Exception {ex.Message}"); 21 } 22 finally 23 { 24 Monitor.Exit(locker); 25 } 26 }
通過上面的代碼,我們可以看出Monitor和lock實作的功能基本一致,但Monitor的使用要明顯比lock更復雜,也行這就是我們平時更多的使用lock,而不是Monitor的原因,
三、Semaphore 信號量
System.Threading.Semaphore
lock和Monitor加鎖之后,每次只能有一個執行緒訪問臨界代碼,信號量類似于一個執行緒池,執行緒訪問之前獲取一個信號,訪問完成釋放信號,只要信號量內有可用信號便可以訪問,否則等待,
建構式:
public Semaphore(int initialCount, int maximumCount) //創建一個信號量,指定初始信號數量和最大信號數量,
幾個重要方法:
public int Release() //代碼注釋的意思是:退出信號量,并回傳之前的(可用信號)數量,實際上,除了退出,這個方法每呼叫一次會增加一個可用信號,但數量達到最大數量時會拋例外,
public int Release(int releaseCount) //和上面的方法類似,上面的方法每次只釋放一個信號,這個方法可以指定信號數量,
public virtual bool WaitOne() //等待一個可用信號
看下面的示例代碼,如果只初始一個信號量,new Semaphore(1, 100),運行結果與lock和Monitor是一樣的,兩個方法交替執行,如果初始信號量為多個時,new Semaphore(3, 100),執行效率高的方法要占用更多的信號,從而執行更多次,
1 int counter = 0; 2 int semaphoreCount = 0; 3 Semaphore semaphore = new Semaphore(3, 100); 4 5 private void Add() 6 { 7 semaphore.WaitOne(); 8 Thread.Sleep(1000); 9 counter++; 10 semaphoreCount = semaphore.Release(); 11 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Add counter={counter}.SemaphoreCount:{semaphoreCount}"); 12 } 13 14 private void Minus() 15 { 16 semaphore.WaitOne(); 17 Thread.Sleep(2000); 18 counter--; 19 semaphore.Release(); 20 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Minus counter={counter}.SemaphoreCount:{semaphoreCount}"); 21 }
Semaphore在生產者/消費者模式下的應用
生產者每次添加一個信號,消費者每次消耗一個信號,如果信號量為0,則消費者進入等待狀態,
1 int counter = 0; 2 int semaphoreCount = 0; 3 Semaphore semaphore = new Semaphore(0, int.MaxValue); 4 5 private void Product() 6 { 7 semaphoreCount = semaphore.Release(); 8 Thread.Sleep(1000); 9 counter++; 10 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Product counter={counter}.SemaphoreCount:{semaphoreCount}"); 11 } 12 13 private void Consume() 14 { 15 semaphore.WaitOne(); 16 Thread.Sleep(2000); 17 counter--; 18 this.logger.LogDebug($"{DateTime.Now.ToLongTimeString()} Consume counter={counter}.SemaphoreCount:{semaphoreCount}"); 19 }
四、ConcurrentQueue 和 Queue
.net 集合中有一類執行緒安全的集合 System.Collections.Concurrent,ConcurrentQueue 就是其中的一個,執行緒安全的佇列,有普通佇列Queue先進先出的特點,同時又具備多執行緒安全,
測驗程序中發現:
Queue 類的兩個出佇列方法 Dequeue() 和 TryDequeue(out result),在多執行緒環境下,Dequeue() 會出現并發訪問錯誤,但TryDequeue(out result)不會,即TryDequeue(out result)即使不加鎖,在多執行緒環境下也運行正常,
ConcurrentQueue 類只有一個出佇列方法 TryDequeue(out result),當然,是執行緒安全的,
五、BlockingQueue
BlockingQueue并發.net內置的類,有人說到這個類,那他多半是在說BlockingCollection
BlockingQueue有一篇很不錯的文章,可以參考一下:
https://docs.microsoft.com/zh-cn/archive/blogs/toub/blocking-queues
六、BlockingCollection
BlockingCollection是.net內置的類,相當于帶有阻塞功能的ConcurrentQueue ,相比較ConcurrentQueue ,BlockingCollection在從佇列中讀取資料時,如果佇列為空,那么它會等待(block),直到有資料可讀取,
而ConcurrentQueue ,需要我們自行判斷是否讀取了資料,并且控制回圈讀取的頻率,
.net 檔案對這個類解釋的非常詳細,可以仔細閱讀:
https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.blockingcollection-1?view=netcore-3.1
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/39672.html
標籤:.NET Core
上一篇:OAuth2.0 基礎知識
