关于c#多线程中的几个信号量
先言
信号量在c#多线程通信中主要用来向阻塞的线程传达信号从而使得阻塞线程继续执行
多线程信号(线程交互):通常是指线程必须等待一个线程或者多个线程通知交互(释放信号)才可以继续执行
在c#中信号量主要有这几个 AutoResetEvent,ManualResetEvent,CountdownEvent,EventWaitHandle,Semaphore
信号量
信号量状态,有信号状态即不会阻塞线程、无信号状态会去阻塞线程。wainOne方法会阻塞线程,当waitOne方法具有阻塞时间量的时候则无需等待信号量的释放,等时间到了会去自动执行waitOne方法后的语句
AutoResetEvent
AutoResetEvent 在释放信号量后,会默认设置为无信号状态。AutoResetEvent 构造函数会传递一个initialState boolean 类型的参数,参数为false 时 需要主动去传递信号量,传递信号量之后将重新设置为无信号状态。参数为ture 时会自动设置为有信号状态,大体意思就是,会默认执行阻塞线程,不需要阻塞线程收到信号量才会执 行(不会阻塞调用线程)。在参数为ture 时,AutoResetEvent 类实例调用 Reset () 方法后,会将当前AutoResetEvent 类实例设置为无信号状态也就是 变成了一个 参数为 false 的 AutoResetEvent 类实例,在此之后的执行阻塞线程都需要主动去释放(传递)信号。
1 | private static AutoResetEvent auto = new AutoResetEvent(false); |
1 | Thread thread1 = new Thread(AutoResetEventHandler); |
1 | private static void AutoResetEventHandler() |
ManualResetEvent
ManualResetEvent 与上面的AutoResetEvent 类似在构造函数中也会传入一个Boolean类型参数,不同的是信号量的释放,AutoResetEvent在信号量释放后会自动设置为无信号状态(未终止状态),ManualResetEvent 需要我们手动调用Reset()方法将其设置为无信号量状态(未终止状态),否则其会一直保持有信号量状态(终止状态)ManualResetEvent 如果不手动重置信号量状态,阻塞线程将不会起作用,会立即执行。这里有个注意点,EventReseMode.ManualReset等待句柄上有一个或多个等待线程,我们要注意Rese()的时机,等待线程恢复执行前是需要一定的执行时间的,我们无法判断那个等待线程恢复到执行前,在调用Reset()方法可能会中断等待线程的执行。如果我们希望在所有的等待线程都执行完后开启新的线程,就必须将他阻止到等待线程都完成后去发送新的信号量执行新的任务。AutoResetEvent 不会存在这个情况,因为它会自动重置为无信号状态。这里我们可以看看官方的说法
1 | private static ManualResetEvent manualReset = new ManualResetEvent(false); |
1 | Thread thread1 = new Thread(() => { |
1 | private static void ManualResetEventHandler1() |
上面说到过,ManualResetEvent 构造函数与AutoResetEvent构造函数是一样,通过bool类型的参数判读 类的实例是否默认释放信号量,不同的是ManualResetEvent 需要手动调用Reset()方法。上面代码中,我们传递了一个false参数,调用了Set()方法释放信号量,然后再调用Reset()方法重置信号量,如此反复一次,ManualResetEventHandler2 会一直阻塞 直到我们释放信号量,才会继续执行。
CountdownEvent
CountdownEvent 实例化是需要传入一个int 类型作为InitialCount初始值,CountdownEvent信号量的释放很特别,只有当Countdown类的实例的CurrentCount等于0时才会释放我们的信号量,Signal()方法每次调用都会使得CurrentCount进行-1操作。Reset()方法会重置为实例化对象时传递的参数值,也可以Reset(100)对我们的InitialCount重新赋值。
1 | private static CountdownEvent countdownEvent = new CountdownEvent(1000); |
1 | CountReduce(); |
1 | private static async Task CountReduce() |
上面代码中我们有用到异步方法但没有等待结果,但是但是线程的委托方法中调用了 countdownEvent.Wait()来阻塞线程;,只有当我们的CurrentCount等于0时才会释放信号量线程才不会阻塞得以继续执行(有感兴趣的可以试试这部分的代码)
EventWaitHandle
本地事件等待句柄是指创建EventWaitHandle 对象时指定EventResetMode枚举,可分为自动重置的事件等待句柄和手动重置的事件等待句柄。
关于事件等待句柄,不涉及到.NET 事件以及委托和事件处理程序,我们可以看一下官方的声明。
EventWaitHandle 微软的解释是本地事件等待句柄,但这和c#中的事件是毫无关系的,这里指的是操作系统的事件。EventWaitHandle 实例化需要两个参数Bool、EventResetMode 。布尔类型的参数是指是否具有信号量,EventMode 是个枚举,枚举值有AutoResetEvent,ManulResetEvent,到这里的用法就跟AutoResetEvent、MnaulResetEvent 差不多了。
SignalAndWait
EventWaitHandle 是继承WaitHandle的,WaitHandler又实现了许多静态,SignalAndWait 就是其中一个。
SignalAndWait 需要传递的两个参数都是WaitHandle 的实例,第一个参数是指需要传递信号量的watihandle 实例,也就是说无论我这个handler 之前是有状态的还是无状态的,他都会去执行,第二个参数是指需要中断的handler
1 | EventWaitHandle manulEventWaitHandler = new EventWaitHandle(false,EventResetMode.ManualReset); |
1 | void EventWaitHandlerAuto() |
1 | void EventWaitHandlerManul() |
Semaphore
Semaphore 可以限制同时进入的线程数量。Semaphore 的构造函数有两个int 类型的参数,第一是指允许同时进入线程的个数,第二个是指最多与同时进入线程的个数,并且第二个参数时不能小于第一个参数(毕竟同时进入的不能大于最大能容纳下的)。WaitOne()方法这里的与上面几个信号量有点小小的不同,每调用一次Semaphore释放的信号灯数量减一,当信号灯数量为0时会阻塞线程,Release()方法会对我们的信号灯数量进行加一操作(释放信号灯),也可以调用Release(int i)来指定释放的信号灯数量。这里有个注意点,我们可以在程序中多次调用Release方法(),但要保证在程序中释放的信号量不能大于最大信号量。
1 | private static Semaphore semaphore = new Semaphore(2, 5);//本地信号灯 |
1 | for (var i = 0; i < 12; i++) |
1 | private static void Semaphorehandle(Object i) |
这里插一句——多线程执行是没有特定的顺序的、是不可预测的。
Semaphore信号灯有两种:本地信号灯和命名系统信号灯。本地信号灯仅存在于进程中(上面的例子中使用的是本地信号灯)。命名系统信号灯是存在与整个操作系统的,一般用于同步进程的活动。
c#多线程的信号量就先到这了。
也可以看大佬的教程
本文涉及到的Demo代码
写在最后
写到这里,应该是距写这篇文章差不多也有10个月左右了,因为最近在找工作的原因,又来回顾这篇文章了,结果发现了有许多看不懂的地方和错误,目前也已改正,也许还存在部分错误我无法发现,若发现错误还望各位告知,并且也告诉自己做技术写文章需要严谨的态度的。

