.Net SemaphoreSlim
看Elsa-core源程式碼中看到的,Elsa-core中所有保存數據的方法似乎使用同一個Save方法。如下圖:
那麼為什麼要使用這玩意,我還是頭一次見這玩意????
好吧,我承認我自己菜。我自個兒也該保持謙虛態度學習學習了。
先看下這個SemaphoreSlim類的描述
(我買的正版Resharp 2022)反編譯程式碼如上圖。
翻譯過來就是 「限制當前訪問資源或池中資源的執行緒數」,真的是這樣的嗎? 試一試…
我的程式碼倉庫 //github.com/qiqiqiyaya/Learning-Case/tree/main/SemaphoreSlim ,測試例子。
例一
在B乎中看到一篇文章 ,鏈接地址 //zhuanlan.zhihu.com/p/158777952
我copy他的例子測試了下,如下圖:
// 現在有10個人要過橋 // 但是一座橋上只能承受5個人,再多橋就會塌 static void SemaphoreTest() { var semaphore = new SemaphoreSlim(5); for (int i = 1; i <= 10; i++) { Thread.Sleep(100); // 排隊上橋 var index = i; // 定義index 避免出現閉包的問題 Task.Run(() => { semaphore.Wait(); try { Console.WriteLine($"第{index}個人正在過橋。"); Thread.Sleep(5000); // 模擬過橋需要花費的時間 } finally { Console.WriteLine($"第{index}個人已經過橋。"); semaphore.Release(); } }); } }
運行結果,與B乎上作者的結果一樣。
這裡有個問題,該作者使用Task.Run 然後在其中添加了 Thread.Sleep(5000); ,這會阻塞當前的執行緒,導致執行 Task 任務的「任務調度器」開啟了 10個執行緒。
關於什麼是 Task ,理解 Task ,請閱讀大佬的文章,非常Nice鏈接 //www.cnblogs.com/artech/p/task_scheduling.html
列二,如果在非同步Async/Await中是什麼情況呢?
static void SemaphoreTest1() { var semaphore = new SemaphoreSlim(5); for (int i = 1; i <= 10; i++) { Thread.Sleep(100); // 排隊上橋 var index = i; // 定義index 避免出現閉包的問題 Task.Run(async () => { Console.WriteLine($"第{index}個人已抵達橋邊上。執行緒Id " + Thread.CurrentThread.ManagedThreadId); semaphore.Wait(); try { Console.WriteLine($"第{index}個人正在過橋。執行緒Id " + Thread.CurrentThread.ManagedThreadId); //Thread.Sleep(5000); // 模擬過橋需要花費的時間 await Task.Delay(5000); } finally { Console.WriteLine($"第{index}個人已經過橋。執行緒Id " + Thread.CurrentThread.ManagedThreadId); semaphore.Release(); } }); } }
將 Thread.Sleep(5000); 換成了 await Task.Delay(5000); 使用 async/await 。結果如下:
從結果上看,例如:第1個人 執行緒Id 3 上橋,在執行 await Task.Delay(5000); (模擬過橋需要花費的時間)後,執行緒Id變成了 12 。這是執行 Task 的任務調度器(Task默認調度器是執行緒池)作用的效果。這不是本次隨筆的重點。
再次強調關於理解Task,請參考 //www.cnblogs.com/artech/p/task_scheduling.html 。
再次強調Task與執行緒Thread是兩個東西,Task可以理解為一個任務,那麼這個任務由Thread去執行,至於由哪一個Thread 去執行,這就由 「任務調度器」 去決定了。
從結果上看,執行了五個Task之後,就阻止後續Task再往下執行程式碼了。
列二中使用了 semaphore.Wait(); 同步阻塞 , 導致後續Task中使用一個之前空閑的Id為3的執行緒 (Id為3的執行緒執行到 await Task.Delay(5000) 後阻塞了,該執行緒被調度器拿去執行新的Task任務),並創建了另外4個新的執行Task的執行緒。
結論:
1.SemaphoreSlim會限制訪問資源的執行緒數
2.在非同步async/await情況下,SemaphoreSlim會限制正在執行訪問資源的Task的數量
例三
static void SemaphoreTest2() { var semaphore = new SemaphoreSlim(5); for (int i = 1; i <= 10; i++) { Thread.Sleep(100); // 排隊上橋 var index = i; // 定義index 避免出現閉包的問題 Task.Run(async () => { Console.WriteLine($"第{index}個人已抵達橋邊上。執行緒Id " + Thread.CurrentThread.ManagedThreadId); await semaphore.WaitAsync(); try { Console.WriteLine($"第{index}個人正在過橋。執行緒Id " + Thread.CurrentThread.ManagedThreadId); await Task.Delay(5000);// 模擬過橋需要花費的時間 } finally { Console.WriteLine($"第{index}個人已經過橋。執行緒Id " + Thread.CurrentThread.ManagedThreadId); semaphore.Release(); } }); } }
執行結果
例三中我使用了 await semaphore.WaitAsync(); ,也就是說不存在同步阻塞程式碼了,全部都是非同步。
非同步下遇到await,執行等待,那麼當前執行這個Task的執行緒可能會被「任務調度器拿去執行別的Task」
結論:
1.在非同步async/await情況下,SemaphoreSlim會限制正在執行訪問資源的Task的數量, 與例二第二個結論一致。
非同步下的Lock鎖
鑒於SemaphoreSlim的特殊性,可以使用其通過程式角度來實現「鎖」(叫樂觀鎖、還是悲觀鎖???,我去,兩者好像都不是)
樂觀鎖與悲觀鎖定義如下圖
如下圖程式碼:
重點程式碼 SemaphoreSlim _semaphore = new(1)
如果真要說明,我覺得是最多像 悲觀鎖 ,但又不全是。這裡的SemaphoreSlim只是說同一時刻只准一個執行緒或正在執行的Task做保存操作,那麼如果是分散式程式呢,另一個程式此時也執行保存操作?
我覺得其真實原因是:使用SemaphoreSlim可以實現非同步下的 Lock 鎖。