#信號量在c#多線程通信中主要用來向阻塞的線程傳達信號從而使得阻塞線程繼續執行 多線程信號(線程交互):通常是指線程必須等待一個線程或者多個線程通知交互(釋放信號)才可以繼續執行 在c#中信號量主要有這幾個 AutoResetEvent,ManualResetEvent,CountdownEvent ...
信號量在c#多線程通信中主要用來向阻塞的線程傳達信號從而使得阻塞線程繼續執行
多線程信號(線程交互):通常是指線程必須等待一個線程或者多個線程通知交互(釋放信號)才可以繼續執行
在c#中信號量主要有這幾個 AutoResetEvent,ManualResetEvent,CountdownEvent,EventWaitHandle,Semaphore
AutoResetEvent
AutoResetEvent 在釋放信號量後,會預設設置為無信號狀態。AutoResetEvent 構造函數會傳遞一個initialState boolean 類型的參數,參數為false 時 需要主動去傳遞信
號量,傳遞信號量之後將重新設置為無信號狀態。參數為ture 時會自動設置為有信號狀態(終止狀態),大體意思就是,會預設執行阻塞線程,不需要阻塞線程收到信號量才會執
行(不會阻塞調用線程)。在參數為ture 時,AutoResetEvent 類實例調用 Reset () 方法後,會將當前AutoResetEvent 類實例設置為無信號狀態也就是 變成了一個 參數為
false 的 AutoResetEvent 類實例,在此之後的執行阻塞線程都需要主動去釋放(傳遞)信號。
private static AutoResetEvent auto = new AutoResetEvent(false);
private static AutoResetEvent auto = new AutoResetEvent(ture);//有信號終止狀態
Thread thread1 = new Thread(AutoResetEventHandler);
Console.WriteLine("當前線程id"+Thread.CurrentThread.ManagedThreadId);
thread1.Start();
Thread.Sleep(5000);
auto.Set();
// auto.Reset(); 在這種情況下new AutoResetEvent(ture) 的 類實例 會變成無信號未終止狀態的 如果阻塞線程沒有接收到信號量將會一直阻塞下去,直到接收到信號量
Thread thread2 = new Thread(AutoResetEventHandlerTwo);
thread2.Start();
Thread.Sleep(3000);//等待3秒
private static void AutoResetEventHandler()
{
Console.WriteLine("當前線程id" + Thread.CurrentThread.ManagedThreadId);
auto.WaitOne();//阻塞線程
Console.WriteLine("等待一秒後執行");
}
private static void AutoResetEventHandlerTwo()
{
auto.WaitOne();//阻塞線程
Console.WriteLine("我是第二個等待執行");
}
ManualResetEvent
ManualResetEvent 與上面的AutoResetEvent 類似在構造函數中也會傳入一個Boolean類型參數,不同的是信號量的釋放,AutoResetEvent在信號量釋放後會自動設置為無信號狀態(未終止狀態),ManualResetEvent 需要我們手動調用Reset()方法將其設置為無信號量狀態(未終止狀態),否則其會一直保持有信號量狀態(終止狀態)ManualResetEvent 如果不手動重置信號量狀態,阻塞線程將不會起作用,會立即執行。
private static ManualResetEvent manualReset = new ManualResetEvent(false);
private static ManualResetEvent manualReset = new ManualResetEvent(true);
Thread thread1 = new Thread(() => {
manualReset.WaitOne();
Console.WriteLine("最開始的執行");
});
thread1.Start();
Thread.Sleep(3000);//休眠--等待三秒
manualReset.Set();//釋放信號量
Thread thread2 = new Thread(ManualResetEventHandler1);
thread2.Start();
manualReset.Reset();//充值信號量
Thread thread3=new Thread(ManualResetEventHandler2);
manualReset.Set();//釋放信號量
thread3.Start();
manualReset.Reset();
private static void ManualResetEventHandler1()
{
manualReset.WaitOne();
Console.WriteLine("第一次等待執行");
}
private static void ManualResetEventHandler2()
{
manualReset.WaitOne();
Console.WriteLine("第二次等待執行");
}
上面說到過,ManualResetEvent 構造函數與AutoResetEvent構造函數是一樣,通過bool類型的參數判讀 類的實例是否預設釋放信號量,不同的是ManualResetEvent 需要手動調用Reset()方法。上面代碼中,我們傳遞了一個false參數,調用了Set()方法釋放信號量,然後再調用Reset()方法重置信號量,如此反覆一次,ManualResetEventHandler2 會一直阻塞 直到我們釋放信號量,才會繼續執行。
CountdownEvent
CountdownEvent 實例化是需要傳入一個int 類型作為InitialCount初始值,CountdownEvent信號量的釋放很特別,只有當Countdown類的實例的CurrentCount等於0時才會釋放我們的信號量,Signal()方法每次調用都會使得CurrentCount進行-1操作。Reset()方法會重置為實例化對象時傳遞的參數值,也可以Reset(100)對我們的InitialCount重新賦值。
private static CountdownEvent countdownEvent = new CountdownEvent(1000);
CountReduce();
// countdownThread.Start();
Thread thread = new Thread(() => {
countdownEvent.Wait();
Console.WriteLine("直到CountdownEvent總數="+countdownEvent.CurrentCount+"我才執行");
//CountdownEvent.CurrentCount//當前總數
//CountdownEvent.AddCount()//添加1
//CountdownEvent.AddCount(10);//添加指定數量
//CountdownEvent.InitialCount//總數
//CountdownEvent.Reset()//設置為InitialCount初始值
//CountdownEvent.Reset(100)//設置為指定初始值
});
thread.Start();
private static async Task CountReduce()
{
await Task.Run(async () => {
for(var i = 0; i < 1000; i++)
{
await Task.Delay(100);//休眠100毫秒--等到100毫秒
//if (countdownEvent.CurrentCount < 10)
//{
// countdownEvent.Reset(100);
// CountReduce();
//}
countdownEvent.Signal();
Console.WriteLine("當前總數"+countdownEvent.CurrentCount);
}
});
}
上面代碼中我們有用到非同步方法但沒有等待結果,但是但是線程的委托方法中調用了 countdownEvent.Wait()來阻塞線程;,只有當我們的CurrentCount等於0時才會釋放信號量線程才不會阻塞得以繼續執行(有感興趣的可以試試這部分的代碼)
EventWaitHandle
本地事件等待句柄是指創建EventWaitHandle 對象時指定EventResetMode枚舉,可分為自動重置的事件等待句柄和手動重置的事件等待句柄。
關於事件等待句柄,不涉及到.NET 事件以及委托和事件處理程式,我們可以看一下官方的聲明。
EvenetResetMode.AutoReset
看到AutoReset是不是想起了,我們上面的AutoResetEvent,其用法是一樣的。在創建EventWaitHanlde對象時來指定是否自動重置信號狀態。此同步事件表示一個等待線程(阻塞線程)在收到信號時自動重置信號狀態。此事件向等待線程發送信號時,需要調用Set()方法
EvenetResetMode.ManualReset
在創建EventWaitHandle對象時指定手動重置信號狀態。事件收到信號時手動重置信號狀態,調用Set()方法釋放信號。在調用ReSet()方法前,在此事件等待句柄上的一個或多個等待線程(阻塞線程)收到信號,立即繼續執行,並且此時的等待事件句柄一直時保持信號狀態(終止狀態)。這裡有個註意點,EventReseMode.ManualReset等待句柄上有一個或多個等待線程,我們要註意Rese()的時機,等待線程恢復執行前是需要一定的執行時間的,我們無法判斷那個等待線程恢復到執行前,在調用Reset()方法可能會中斷等待線程的執行。如果我們希望在所有的等待線程都執行完後開啟新的線程,就必須將他組織到等待線程都完成後去發送新的信號量執行新的任務。
這裡我們可以看看官方的說法
private static EventWaitHandle EventWaitHandle=new EventWaitHandle(false,EventResetMode.ManualReset);
Thread thread1 = new Thread(EventWaitHandle1);
Thread thread2 = new Thread(EventWaitHandle2);
Thread thread3 = new Thread(EventWaitHandle3);
thread1.Start();
thread2.Start();
thread3.Start();
EventWaitHandle.Set();
Thread thread4 = new Thread(EventWaitHandl4);
Thread.Sleep(1000);
thread4.Start();
EventWaitHandle.Reset();
private static void EventWaitHandle1()
{
EventWaitHandle.WaitOne();
Thread.Sleep(1000);
Console.WriteLine("我是第1個EventWaitHandle");
}
private static void EventWaitHandle2()
{
EventWaitHandle.WaitOne();
Thread.Sleep(2000);
Console.WriteLine("我是第2個EventWaitHandle");
}
private static void EventWaitHandle3()
{
EventWaitHandle.WaitOne();
Thread.Sleep(3000);
Console.WriteLine("我是第3個EventWaitHandle");
}
private static void EventWaitHandl4()
{
Thread.Sleep(3000);
EventWaitHandle.WaitOne();
Console.WriteLine("我是第4個EventWaitHandle");
}
這裡著重說一下EventWaitHandle4,從上面可以看到,在thread4線程開始後就開始調用了Reset方法,並且在EventWaitHandle裡面休眠了三秒,這時候EventWaitHandle無法接收到信號量會一直等待下去直到接收到新的信號量。
Semaphore
Semaphore 可以限制同時進入的線程數量。Semaphore 的構造函數有兩個int 類型的參數,第一是指允許同時進入線程的個數,第二個是指最多與同時進入線程的個數,並且第二個參數時不能小於第一個參數(畢竟同時進入的不能大於最大能容納下的)。WaitOne()方法這裡的與上面幾個信號量有點小小的不同,每調用一次Semaphore釋放的信號燈數量減一,當信號燈數量為0時會阻塞線程,Release()方法會對我們的信號燈數量進行加一操作(釋放信號燈),也可以調用Release(int i)來指定釋放的信號燈數量。這裡有個註意點,我們可以在程式中多次調用Release方法(),但要保證在程式中釋放的信號量不能大於最大信號量。
private static Semaphore semaphore = new Semaphore(2, 5);//本地信號燈
for (var i = 0; i < 12; i++)
{
Thread thread = new Thread(new ParameterizedThreadStart(Semaphorehandle));
thread.Start(i);
}
private static void Semaphorehandle(Object i)
{
semaphore.WaitOne();
Console.WriteLine((int)i + "進入了線程");
Thread.Sleep(2000);
Console.WriteLine((int)i + "準備離開線程");
if ((int)i >1)
{
Console.WriteLine(semaphore.Release());
return;
}
semaphore.Release(2);
}
這裡插一句——多線程執行是沒有特定的順序的、是不可預測的。
Semaphore信號燈有兩種:本地信號燈和命名系統信號燈。本地信號燈僅存在於進程中(上面的例子中使用的是本地信號燈)。命名系統信號燈是存在與整個操作系統的,一般用於同步進程的活動。
c#多線程的信號量就先到這了。
也可以看大佬的教程
本文涉及到的Demo代碼