C#多線程(5):資源池限制

Semaphore、SemaphoreSlim 類

二者均可以限制同時訪問某一資源或資源池的線程數。編程

這裏先不扯理論,咱們從案例入手,經過示例代碼,慢慢深刻了解。併發

Semaphore 類

這裏,先列出 Semaphore 類經常使用的 API。函數

其構造函數以下:學習

構造函數 說明
Semaphore(Int32, Int32) 初始化 Semaphore 類的新實例,並指定初始入口數和最大併發入口數。
Semaphore(Int32, Int32, String) 初始化 Semaphore 類的新實例,並指定初始入口數和最大併發入口數,根據須要指定系統信號燈對象的名稱。
Semaphore(Int32, Int32, String, Boolean) 初始化 Semaphore 類的新實例,並指定初始入口數和最大併發入口數,還能夠選擇指定系統信號量對象的名稱,以及指定一個變量來接收指示是否建立了新系統信號量的值。

Semaphore 使用純粹的內核時間(kernel-time)方式(等待時間很短),而且支持在不一樣的進程間同步線程(像Mutex)。操作系統

Semaphore 經常使用方法以下:線程

方法 說明
Close() 釋放由當前 WaitHandle佔用的全部資源。
OpenExisting(String) 打開指定名稱爲信號量(若是已經存在)。
Release() 退出信號量並返回前一個計數。
Release(Int32) 以指定的次數退出信號量並返回前一個計數。
TryOpenExisting(String, Semaphore) 打開指定名稱爲信號量(若是已經存在),並返回指示操做是否成功的值。
WaitOne() 阻止當前線程,直到當前 WaitHandle 收到信號。
WaitOne(Int32) 阻止當前線程,直到當前 WaitHandle 收到信號,同時使用 32 位帶符號整數指定時間間隔(以毫秒爲單位)。
WaitOne(Int32, Boolean) 阻止當前線程,直到當前的 WaitHandle 收到信號爲止,同時使用 32 位帶符號整數指定時間間隔,並指定是否在等待以前退出同步域。
WaitOne(TimeSpan) 阻止當前線程,直到當前實例收到信號,同時使用 TimeSpan 指定時間間隔。
WaitOne(TimeSpan, Boolean) 阻止當前線程,直到當前實例收到信號爲止,同時使用 TimeSpan 指定時間間隔,並指定是否在等待以前退出同步域。

示例

咱們來直接寫代碼,這裏使用 《原子操做 Interlocked》 中的示例,如今咱們要求,採用多個線程執行計算,可是隻容許最多三個線程同時執行運行。code

使用 Semaphore ,有四個個步驟:對象

new 實例化 Semaphore,並設置最大線程數、初始化時可進入線程數;blog

使用 .WaitOne(); 獲取進入權限(在得到進入權限前,線程處於阻塞狀態)。隊列

離開時使用 Release() 釋放佔用。

Close() 釋放Semaphore 對象。

《原子操做 Interlocked》 中的示例改進以下:

class Program
    {
        // 求和
        private static int sum = 0;
        private static Semaphore _pool;

        // 判斷十個線程是否結束了。
        private static int isComplete = 0;
        // 第一個程序
        static void Main(string[] args)
        {
            Console.WriteLine("執行程序");

            // 設置容許最大三個線程進入資源池
            // 一開始設置爲0,就是初始化時容許幾個線程進入
            // 這裏設置爲0,後面按下按鍵時,能夠放通三個線程
            _pool = new Semaphore(0, 3);
            for (int i = 0; i < 10; i++)
            {
                Thread thread = new Thread(new ParameterizedThreadStart(AddOne));
                thread.Start(i + 1);
            }
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("任意按下鍵(不要按關機鍵),能夠打開資源池");
            Console.ForegroundColor = ConsoleColor.White;
            Console.ReadKey();

            // 准許三個線程進入
            _pool.Release(3);

            // 這裏沒有任何意義,就單純爲了演示查看結果。
            // 等待全部線程完成任務
            while (true)
            {
                if (isComplete >= 10)
                    break;
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
            Console.WriteLine("sum = " + sum);

            // 釋放池
            _pool.Close();
            
        }

        public static void AddOne(object n)
        {
            Console.WriteLine($"    線程{(int)n}啓動,進入隊列");
            // 進入隊列等待
            _pool.WaitOne();
            Console.WriteLine($"第{(int)n}個線程進入資源池");
            // 進入資源池
            for (int i = 0; i < 10; i++)
            {
                Interlocked.Add(ref sum, 1);
                Thread.Sleep(TimeSpan.FromMilliseconds(500));
            }
            // 解除佔用的資源池
            _pool.Release();
            isComplete += 1;
            Console.WriteLine($"                     第{(int)n}個線程退出資源池");
        }
    }

看着代碼有點多,快去運行一下,看看結果。

示例說明

實例化 Semaphore 使用了new Semaphore(0,3); ,其構造函數原型爲

public Semaphore(int initialCount, int maximumCount);

initialCount 表示一開始容許幾個進程進入資源池,若是設置爲0,全部線程都不能進入,要一直等資源池放通。

maximumCount 表示最大容許幾個線程進入資源池。

Release() 表示退出信號量並返回前一個計數。這個計數指的是資源池還能夠進入多少個線程。

能夠看一下下面的示例:

private static Semaphore _pool;
        static void Main(string[] args)
        {
            _pool = new Semaphore(0, 5);
            _pool.Release(5);
            new Thread(AddOne).Start();
            Thread.Sleep(TimeSpan.FromSeconds(10));
            _pool.Close();
        }

        public static void AddOne()
        {
            _pool.WaitOne();
            Thread.Sleep(1000);
            int count = _pool.Release();
            Console.WriteLine("在此線程退出資源池前,資源池還有多少線程能夠進入?" + count);
        }

信號量

前面咱們學習到 Mutex,這個類是全局操做系統起做用的。咱們從 Mutex 和 Semphore 中,也看到了 信號量這個東西。

信號量分爲兩種類型:本地信號量和命名系統信號量。

  • 命名系統信號量在整個操做系統中都可見,可用於同步進程的活動。
  • 局部信號量僅存在於進程內。

當 name 爲 null 或者爲空時,Mutex 的信號量時局部信號量,不然 Mutex 的信號量是命名系統信號量。

Semaphore 的話,也是兩種狀況都有。

若是使用接受名稱的構造函數建立 Semaphor 對象,則該對象將與該名稱的操做系統信號量關聯。

兩個構造函數:

Semaphore(Int32, Int32, String)
Semaphore(Int32, Int32, String, Boolean)

上面的構造函數能夠建立多個表示同一命名系統信號量的 Semaphore 對象,並可使用 OpenExisting 方法打開現有的已命名系統信號量。

咱們上面使用的示例就是局部信號量,進程中引用本地 Semaphore 對象的全部線程均可以使用。 每一個 Semaphore 對象都是單獨的本地信號量。

SemaphoreSlim類

SemaphoreSlim 跟 Semaphore 有啥關係?

我看一下書再回答你。

哦哦哦,微軟文檔說:

SemaphoreSlim 表示對可同時訪問資源或資源池的線程數加以限制的 Semaphore 的輕量替代。

SemaphoreSlim 不使用信號量,不支持進程間同步,只能在進程內使用。

它有兩個構造函數:

構造函數 說明
SemaphoreSlim(Int32) 初始化 SemaphoreSlim 類的新實例,以指定可同時授予的請求的初始數量。
SemaphoreSlim(Int32, Int32) 初始化 SemaphoreSlim 類的新實例,同時指定可同時授予的請求的初始數量和最大數量。

示例

咱們改造一下前面 Semaphore 中的示例:

class Program
    {
        // 求和
        private static int sum = 0;
        private static SemaphoreSlim _pool;

        // 判斷十個線程是否結束了。
        private static int isComplete = 0;
        static void Main(string[] args)
        {
            Console.WriteLine("執行程序");

            // 設置容許最大三個線程進入資源池
            // 一開始設置爲0,就是初始化時容許幾個線程進入
            // 這裏設置爲0,後面按下按鍵時,能夠放通三個線程
            _pool = new SemaphoreSlim(0, 3);
            for (int i = 0; i < 10; i++)
            {
                Thread thread = new Thread(new ParameterizedThreadStart(AddOne));
                thread.Start(i + 1);
            }

            Console.WriteLine("任意按下鍵(不要按關機鍵),能夠打開資源池");
            Console.ReadKey();
            // 
            _pool.Release(3);

            // 這裏沒有任何意義,就單純爲了演示查看結果。
            // 等待全部線程完成任務
            while (true)
            {
                if (isComplete >= 10)
                    break;
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
            Console.WriteLine("sum = " + sum);
            // 釋放池
        }

        public static void AddOne(object n)
        {
            Console.WriteLine($"    線程{(int)n}啓動,進入隊列");
            // 進入隊列等待
            _pool.Wait();
            Console.WriteLine($"第{(int)n}個線程進入資源池");
            // 進入資源池
            for (int i = 0; i < 10; i++)
            {
                Interlocked.Add(ref sum, 1);
                Thread.Sleep(TimeSpan.FromMilliseconds(200));
            }
            // 解除佔用的資源池
            _pool.Release();
            isComplete += 1;
            Console.WriteLine($"                     第{(int)n}個線程退出資源池");
        }
    }

SemaphoreSlim 不須要 Close()

二者在代碼上的區別是就這麼簡單。

區別

若是使用下面的構造函數實例化 Semaphor(參數name不能爲空),那麼建立的對象在整個操做系統內都有效。

public Semaphore (int initialCount, int maximumCount, string name);

Semaphorslim 則只在進程內內有效。

SemaphoreSlim 類不會對 WaitWaitAsyncRelease 方法的調用強制執行線程或任務標識。

而 Semaphor 類,會對此進行嚴格監控,若是對應調用數量不一致,會出現異常。

此外,若是使用 SemaphoreSlim(Int32 maximumCount) 構造函數來實例化 SemaphoreSlim 對象,獲取其 CurrentCount 屬性,其值可能會大於 maximumCount。 編程人員應負責確保調用一個 Wait 或 WaitAsync 方法,便調用一個 Release。

這就好像筆筒裏面的筆,沒有監控,使用這使用完畢後,都應該將筆放進去。若是原先有10支筆,每次使用不放進去,或者將別的地方的筆放進去,那麼最後數量就不是10了。

相關文章
相關標籤/搜索