.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 鎖。

Tags: