5

关于c#多线程中的几个信号量 - 阿文不知所措

 2 years ago
source link: https://www.cnblogs.com/lonely-wen/p/16325316.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

信号量在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 事件以及委托和事件处理程序,我们可以看一下官方的声明。

2568376-20220529232003518-1041651781.png

EvenetResetMode.AutoReset

看到AutoReset是不是想起了,我们上面的AutoResetEvent,其用法是一样的。在创建EventWaitHanlde对象时来指定是否自动重置信号状态。此同步事件表示一个等待线程(阻塞线程)在收到信号时自动重置信号状态。此事件向等待线程发送信号时,需要调用Set()方法

EvenetResetMode.ManualReset

在创建EventWaitHandle对象时指定手动重置信号状态。事件收到信号时手动重置信号状态,调用Set()方法释放信号。在调用ReSet()方法前,在此事件等待句柄上的一个或多个等待线程(阻塞线程)收到信号,立即继续执行,并且此时的等待事件句柄一直时保持信号状态(终止状态)。这里有个注意点,EventReseMode.ManualReset等待句柄上有一个或多个等待线程,我们要注意Rese()的时机,等待线程恢复执行前是需要一定的执行时间的,我们无法判断那个等待线程恢复到执行前,在调用Reset()方法可能会中断等待线程的执行。如果我们希望在所有的等待线程都执行完后开启新的线程,就必须将他组织到等待线程都完成后去发送新的信号量执行新的任务。
这里我们可以看看官方的说法

2568376-20220529232158773-402309701.png
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代码


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK