翼度科技»论坛 编程开发 .net 查看内容

并发编程 --- 信号量线程同步

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
引言

上文编码技巧 --- 同步锁对象的选定中,提到了在C#中,让线程同步有两种方式:

  • 锁(lock、Monitor等)
  • 信号量(EventWaitHandle、Semaphore、Mutex)
加锁是最常用的线程同步的方法,就不再讨论,本篇主要讨论使用信号量同步线程。
WaitHandle介绍

实际上,再C#中 EventWaitHandle 、 Semaphore 、 Mutex 都是抽象类 WaitHandle 的派生类,它提供了一组等待信号的方法和属性。如下图:

主要包含静态方法 SignalAndWait(),WaitAll(),WaitAny()及一个虚方法WaitOne()。下面介绍一个这几个方法。
介绍这些方法之前,先简单介绍一下 WaitHandle 的派生类 EventWaitHandle,该派生类有两个实现类 AutoResetEvent 和 ManualResetEvent,其方法列表如下:

重点说一下,Set()和Reset():

  • Set()方法设置事件为有信号状态:当调用 Set() 时,它将被设置为终止状态,并允许一个或多个等待该事件的线程继续执行。
  • Reset()方法设置事件为无信号状态:当调用 Reset() 时,它将被设置为非终止状态,所有想要等待该事件的线程都将被阻塞,直到调用 Set() 方法使其变为终止状态。
注意:这里的有信号,无信号的意思类似于红绿灯,有信号你才能够通行,对于线程来说,有信号意味着可以接着往下运行,无信号则阻塞等待信号。
接下来的代码段演示皆使用 AutoResetEvent 进行演示。
SignalAndWait()

当调用 WaitHandle 的静态方法 SignalAndWait() 时,会使得当前线程等待一个 WaitHandle 对象的信号,同时设置另一个 WaitHandle 对象为有信号状态。当第一个 WaitHandle 对象收到信号时,当前线程继续执行,同时第二个 WaitHandle 对象变为无信号状态。
  1. static AutoResetEvent event1 = new AutoResetEvent(false);
  2. static AutoResetEvent event2 = new AutoResetEvent(false);
  3. static void Main(string[] args)
  4. {
  5.     Thread t1 = new Thread(new ThreadStart(Worker1));
  6.     Thread t2 = new Thread(new ThreadStart(Worker2));
  7.     t1.Start();
  8.     t2.Start();
  9.     Console.ReadLine();
  10. }
  11. static void Worker1()
  12. {
  13.     Console.WriteLine("线程1开始执行……");
  14.     event1.WaitOne(); // 等待事件1的发生
  15.     Console.WriteLine("线程1收到事件1的信号,继续执行……");
  16.     WaitHandle.SignalAndWait(event1, event2); // 发送事件2的信号并等待事件2的发生
  17.     Console.WriteLine("线程1收到事件2的信号,继续执行……");
  18. }
  19. static void Worker2()
  20. {
  21.     Console.WriteLine("线程2开始执行……");
  22.     Thread.Sleep(2000); // 模拟线程2的执行时间
  23.     Console.WriteLine("线程2发出事件1的信号……");
  24.     event1.Set(); // 发送事件1的信号
  25.     Thread.Sleep(2000); // 模拟线程2的执行时间
  26.     Console.WriteLine("线程2发出事件2的信号……");
  27.     WaitHandle.SignalAndWait(event2, event1); // 发送事件1的信号并等待事件1的发生
  28.     Console.WriteLine("线程2收到事件1的信号,继续执行……");
  29. }
复制代码
输出:
  1. 线程1开始执行……
  2. 线程2开始执行……
  3. 线程2发出事件1的信号……
  4. 线程1收到事件1的信号,继续执行……
  5. 线程2发出事件2的信号……
  6. 线程2收到事件1的信号,继续执行……
  7. 线程1收到事件2的信号,继续执行……
复制代码
WaitAll()

当调用 WaitHandle 的静态方法 WaitAll() 时,它可以等待多个WaitHandle对象的信号,直到所有对象都收到信号或等待超时。
  1. static AutoResetEvent[] events = new AutoResetEvent[3]
  2. {
  3.     new AutoResetEvent(false),
  4.     new AutoResetEvent(false),
  5.     new AutoResetEvent(false)
  6. };
  7. static void Main(string[] args)
  8. {
  9.     Thread t1 = new Thread(new ThreadStart(Worker1));
  10.     Thread t2 = new Thread(new ThreadStart(Worker2));
  11.     t1.Start();
  12.     t2.Start();
  13.     Console.ReadLine();
  14. }
  15. static void Worker1()
  16. {
  17.     Console.WriteLine("线程1开始执行……");
  18.     WaitHandle.WaitAll(events); // 等待所有事件的发生
  19.     Console.WriteLine("线程1收到所有事件的信号,继续执行……");
  20. }
  21. static void Worker2()
  22. {
  23.     Console.WriteLine("线程2开始执行……");
  24.     Thread.Sleep(2000); // 模拟线程2的执行时间
  25.     Console.WriteLine("线程2发出事件1的信号……");
  26.     events[0].Set(); // 发送事件1的信号
  27.     Thread.Sleep(2000); // 模拟线程2的执行时间
  28.     Console.WriteLine("线程2发出事件2的信号……");
  29.     events[1].Set(); // 发送事件2的信号
  30.     Thread.Sleep(2000); // 模拟线程2的执行时间
  31.     Console.WriteLine("线程2发出事件3的信号……");
  32.     events[2].Set(); // 发送事件3的信号
  33. }
复制代码
输出:
  1. 线程1开始执行……
  2. 线程2开始执行……
  3. 线程2发出事件1的信号……
  4. 线程2发出事件2的信号……
  5. 线程2发出事件3的信号……
  6. 线程1收到所有事件的信号,继续执行……
复制代码
WaitAny()

当调用 WaitHandle 的静态方法 WaitAny() 时,它可以等待多个WaitHandle对象中的任意一个对象收到信号,直到有一个对象收到信号或等待超时。
  1. static AutoResetEvent[] events = new AutoResetEvent[3]
  2. {
  3.     new AutoResetEvent(false),
  4.     new AutoResetEvent(false),
  5.     new AutoResetEvent(false)
  6. };
  7. static void Main(string[] args)
  8. {
  9.     Thread t1 = new Thread(new ThreadStart(Worker1));
  10.     Thread t2 = new Thread(new ThreadStart(Worker2));
  11.     t1.Start();
  12.     t2.Start();
  13.     Console.ReadLine();
  14. }
  15. static void Worker1()
  16. {
  17.     Console.WriteLine("线程1开始执行……");
  18.     WaitHandle.WaitAny(events); // 等待任意事件的发生
  19.     Console.WriteLine("线程1收到任意事件的信号,继续执行……");
  20. }
  21. static void Worker2()
  22. {
  23.     Console.WriteLine("线程2开始执行……");
  24.     var randomIndex = new Random().Next(0, 2);
  25.     Console.WriteLine("线程2发出任意一个事件的信号……");
  26.     events[randomIndex].Set(); //发送任意一个事件的信号
  27. }
复制代码
输出:
  1. 线程1开始执行……
  2. 线程2开始执行……
  3. 线程2发出任意一个事件的信号……
  4. 线程1收到任意事件的信号,继续执行……
复制代码
WaitOne()

WaitOne()方法上文中其实已经用到了,它就表示阻塞当前线程,等待当前 WaitHandle 对象收到信号,直到对象收到信号或等待超时。如果WaitHandle对象收到信号,WaitOne()方法返回true,否则返回false。使用简单就不在贴代码段。
派生类的异同

上面已经提到了EventWaitHandle 、 Semaphore 、 Mutex 都是抽象类 WaitHandle 的派生类,它们的作用类似,但在使用和实现上有一些不同。下面我们来简单介绍下它们的异同点。

  • EventWaitHandle:
    EventWaitHandle 有两种类型:AutoResetEvent 和 ManualResetEvent。它们的区别在于AutoResetEvent 在有信号时只通知一个等待线程,而 ManualResetEvent 在有信号时通知所有等待线程。
    两者设置为终止状态的方式都是调用 Set() 方法。
  • Semaphore
    Semaphore 可以用于多个线程之间的资源控制。Semaphore 可以控制同时访问共享资源的线程数量。设置为终止状态的方式是调用 Release() 方法。
  • Mutex
    Mutex 可以用于多个线程之间的互斥访问共享资源。Mutex 可以保证同一时间只有一个线程可以访问共享资源。设置为终止状态的方式是调用 ReleaseMutex() 方法。

来源:https://www.cnblogs.com/pandefu/archive/2023/07/18/17536279.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具