二者均可以限制同時訪問某一資源或資源池的線程數。編程
這裏先不扯理論,咱們從案例入手,經過示例代碼,慢慢深刻了解。併發
這裏,先列出 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 跟 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 類不會對 Wait
、WaitAsync
和 Release
方法的調用強制執行線程或任務標識。
而 Semaphor 類,會對此進行嚴格監控,若是對應調用數量不一致,會出現異常。
這就好像筆筒裏面的筆,沒有監控,使用這使用完畢後,都應該將筆放進去。若是原先有10支筆,每次使用不放進去,或者將別的地方的筆放進去,那麼最後數量就不是10了。