天天看點

【.NET深呼吸】線程信号量(Semaphore)

Semaphore類可以控制某個資源允許通路的線程數,Semaphore有命名式的,也有不命名的;如果不考慮跨程序工作,一般在代碼中使用不命名方式即可。

信号量有點類似于等待句柄,某個線程如果調用了WaitOne方法,這個線程就會暫停,并且等待有可用的信号量時才會繼續執行;某個線程調用Release方法,就會釋放一個信号計數值,每調用一次就釋放一個,如果想一次性釋放N個信号,可以調用Release(int)重載,把要釋放的數量傳遞給方法參數,但這個數值不能超過Semaphore執行個體化時所指定的最大值,否則會引發異常。

Semaphore構造函數可以指定允許的最大信号量,以及預設的信号量。聲明如下:

Semaphore(int initialCount, int maximumCount);      

maximumCount參數指定該對象允許的最大信号量;initialCount參數指定預設值,這個預設值不能超過maximumCount指定的最大值。即該Semaphore執行個體預設允許多少個線程收到信号(通路資源)。

當某個占用資源的線程調用Release方法後,它會釋放出一個或多個信号,這時候,其他等待的線程就可以繼續執行。

隻要是涉及到線程問題都特别難說清楚,相當抽象,相當考驗人的了解能力。

比如,圖書館裡面有五本《X瓶梅》,但想借這本書的有20人。前面五個人自然很輕松就借到(進入通路圈,這五個線程以外的線程等待),其他人隻好等了。

過了幾天後,有個家夥通宵看書,終于看完了,是以他還了書,這時候,剩下的15個人看誰的動作快,可以借到剛還回去的這本書。

再過了幾天,又有兩個人看完了,還書。此時,剩下的14個人中,有兩個人可以借得此書。

大概的原理就是這樣,下面看看例子。

class Program
    {
        // 生成随機數,以延遲每個任務的執行時間
        static Random rand = new Random();
        // 聲明Semaphore變量,以控制線程信号量
        static Semaphore sm = null;
        static void Main(string[] args)
        {
            sm = new Semaphore(1, 4); //執行個體化
            // 啟動10個任務
            for (int i = 0; i < 10; i++)
            {
                Task t = new Task(DoWork, "任務" + (i + 1));
                t.Start();
            }

            // 防止DOS視窗立即退出
            Console.Read();
        }

        private static async void DoWork(object p)
        {
            sm.WaitOne(); //等待花開
            string tn = p?.ToString();
            Console.WriteLine($"{tn} 已獲得通路。");
            await Task.Delay(rand.Next(1, 10) * 1000);
            // 釋放
            sm.Release(); //花謝了
            Console.WriteLine($"{tn}已釋放。");
        }
    }      

多線程開發我最喜歡用Task類,友善簡單強大好用高大上,而且它還能自行處理CPU多個核的問題。在上面例子中,有10個任務要執行,但我所執行個體化的Semaphore對象給的最大通路線程數為4,而預設狀态下隻允許1個線程同時通路。

是以,10個任務啟動後,其中一個會搶到通路權,其他任務就等吧。這時候Semaphore對象可通路數為0。因為預設隻允許1,現在有一個線程搶了,是以剩下就是0個通路權了。

當這個搶到通路權的任務調用Release方法後,通路權被釋放,這時候剩下的9個任務就開始搶,誰搶到誰就執行……依此類推。

看看運作結果。

【.NET深呼吸】線程信号量(Semaphore)

任務1手快,它先搶到了通路權,于是它dododo,do完後,調用Release方法釋放,然後任務3人品好,就搶到了通路權,然後XXXXX,X完後調用Release釋放。其他線程繼續搶……

估計看完以上例子後,大家應該有點頭緒了。

現在,我們把上面的代碼改一下,在初始化Semaphore對象時的預設值從1改為3。

sm = new Semaphore(3, 4);      

預設允許3個線程同時通路資源,最大數量為4。

然後再次運作,結果如下:

【.NET深呼吸】線程信号量(Semaphore)

因為預設允許3個線程同時進入,是以在輸出結果中,前面三個任務都能擷取通路權,而其他的任務隻能等待機會。目前面已獲得資源的三個任務中有一個或者N個進行釋放後,剩下的任務又開始搶機會。

本文示例下載下傳位址:https://files.cnblogs.com/files/tcjiaan/DemoApp.zip

繼續閱讀