• 5天不再惧怕多线程——第四天 信号量


        今天整理“信号量”的相关知识,其实想想也蛮有趣的,锁,互斥,信号量都可以实现线程同步,在framework里面主要有三种。

    <1>:ManualResetEvent

    <2>:AutoResetEvent

    <3>: Semaphore

    好,下面就具体看看这些玩意的使用。

    一:ManualResetEvent

          该对象有两种信号量状态True和False,好奇的我们肯定想知道True和False有什么区别,稍后的例子见分晓,有三个方法值得学习一下。

    1:WaitOne

         该方法用于阻塞线程,默认是无限期的阻塞,有时我们并不想这样,而是采取超时阻塞的方法,如果超时就放弃阻塞,这样也就避免了无限期

           等待的尴尬。

    2:Set

         手动修改信号量为True,也就是恢复线程执行。

    3:ReSet

         手动修改信号量为False,暂停线程执行。

    好了,下面举个例子说明一下。

    <1>  信号量初始为False,WaitOne采用无限期阻塞,可以发现线程间可以进行交互。

     1 public class Example
    2 {
    3 public static void Main()
    4 {
    5 Thread t = new Thread(Run);
    6
    7 t.Name = "Jack";
    8
    9 Console.WriteLine("当前时间:{0} {1} {1},我是主线程,收到请回答。", DateTime.Now, t.Name);
    10
    11 t.Start();
    12
    13 Thread.Sleep(5000);
    14
    15 mr.Set();
    16
    17 Console.Read();
    18 }
    19
    20 static ManualResetEvent mr = new ManualResetEvent(false);
    21
    22 static void Run()
    23 {
    24 mr.WaitOne();
    25
    26 Console.WriteLine("\n当前时间:{0} 主线程,主线程,{1}已收到!", DateTime.Now, Thread.CurrentThread.Name);
    27 }
    28 }

    <2> 信号量初始为True,WaitOne采用无限期阻塞,实验发现WaitOne其实并没有被阻塞。

     static ManualResetEvent mr = new ManualResetEvent(true);

    <3>信号量初始为False,WaitOne采用超时2s,虽然主线程要等5s才能进行Set操作,但是WaitOne已经等不及提前执行了。

     1 public class Example
    2 {
    3 public static void Main()
    4 {
    5 Thread t = new Thread(Run);
    6
    7 t.Name = "Jack";
    8
    9 Console.WriteLine("当前时间:{0} {1} {1},我是主线程,收到请回答。", DateTime.Now, t.Name);
    10
    11 t.Start();
    12
    13 Thread.Sleep(5000);
    14
    15 mr.Set();
    16
    17 Console.Read();
    18 }
    19
    20 static ManualResetEvent mr = new ManualResetEvent(false);
    21
    22 static void Run()
    23 {
    24 mr.WaitOne(2000);
    25
    26 Console.WriteLine("\n当前时间:{0} 主线程,主线程,{1}已收到!", DateTime.Now, Thread.CurrentThread.Name);
    27 }
    28 }


    二:AutoResetEvent

          在VS对象浏览器中,我们发现AutoResetEvent和ManualResetEvent都是继承于EventWaitHandle,所以基本功能是一样的,不过值得注意

    的一个区别是WaitOne会改变信号量的值,比如说初始信号量为True,如果WaitOne超时信号量将自动变为False,而ManualResetEvent则不会。

     1 public class Example
    2 {
    3 public static void Main()
    4 {
    5 Thread t = new Thread(Run);
    6
    7 t.Name = "Jack";
    8
    9 t.Start();
    10
    11 Console.Read();
    12 }
    13
    14 static AutoResetEvent ar = new AutoResetEvent(true);
    15
    16 static void Run()
    17 {
    18 var state = ar.WaitOne(1000, true);
    19
    20 Console.WriteLine("我当前的信号量状态:{0}", state);
    21
    22 state = ar.WaitOne(1000, true);
    23
    24 Console.WriteLine("我恨你,不理我,您现在的状态是:{0}", state);
    25
    26 }
    27 }

    三:Semaphore 

         这玩意是.net 4.0新增的,用于控制线程的访问数量,默认的构造函数为initialCount和maximumCount,表示默认设置的信号量个数和

    最大信号量个数,其实说到底,里面是采用计数器来来分配信号量,当你WaitOne的时候,信号量自减,当Release的时候,信号量自增,然而

    当信号量为0的时候,后续的线程就不能拿到WaitOne了,所以必须等待先前的线程通过Release来释放。

    好了,下面还是举例子来说明一下:

    <1> initialCount=1,maximunCount=10,WaitOne采用无限期等待。

     1 namespace ConsoleApplication3
    2 {
    3 class Program
    4 {
    5 static void Main(string[] args)
    6 {
    7
    8 Thread t1 = new Thread(Run1);
    9 t1.Start();
    10
    11 Thread t2 = new Thread(Run2);
    12 t2.Start();
    13
    14 Console.Read();
    15 }
    16
    17 static Semaphore sem = new Semaphore(1, 10);
    18
    19 static void Run1()
    20 {
    21 sem.WaitOne();
    22
    23 Console.WriteLine("大家好,我是Run1");
    24 }
    25
    26 static void Run2()
    27 {
    28 sem.WaitOne();
    29
    30 Console.WriteLine("大家好,我是Run2");
    31 }
    32 }
    33 }

    我们悲剧的发现t2线程不能执行,我们知道WaitOne相当于自减信号量,然而默认的信号量个数为1,所以t2想执行必须等待t1通过Release来释放。

    1         static void Run1()
    2 {
    3 sem.WaitOne();
    4
    5 Console.WriteLine("大家好,我是Run1");
    6
    7 sem.Release();
    8 }

    可能有的同学要问,我不是设置了maximunCount=10吗?为什么没有起到作用?是的,默认情况下是没有起到作用,必须要我们手动干预一下,

    我们知道调用Release方法相当于自增一个信号量,然而Release有一个重载,可以指定自增到maximunCount个信号量,这里我就在主线程上

    Release(10),看看效果。

     1 namespace ConsoleApplication3
    2 {
    3 class Program
    4 {
    5 static void Main(string[] args)
    6 {
    7
    8 Thread t1 = new Thread(Run1);
    9 t1.Start();
    10
    11 Thread t2 = new Thread(Run2);
    12 t2.Start();
    13
    14 Thread.Sleep(1000);
    15
    16 sem.Release(10);
    17
    18 Console.Read();
    19 }
    20
    21 static Semaphore sem = new Semaphore(1, 10);
    22
    23 static void Run1()
    24 {
    25 sem.WaitOne();
    26
    27 Console.WriteLine("大家好,我是Run1");
    28 }
    29
    30 static void Run2()
    31 {
    32 sem.WaitOne();
    33
    34 Console.WriteLine("大家好,我是Run2");
    35 }
    36 }
    37 }


    <2> Semaphore命名,升级进程交互。

          在VS对象浏览器中发现Semaphore是继承字WaitHandle,而WaitHandle封装了win32的一些同步机制,所以当我们给Semaphore命名的时候

    就会在系统中可见,下面举个例子,把下面的代码copy一份,运行两个程序。

     1 namespace ConsoleApplication3
    2 {
    3 class Program
    4 {
    5 static void Main(string[] args)
    6 {
    7
    8 Thread t1 = new Thread(Run1);
    9 t1.Start();
    10
    11 Thread t2 = new Thread(Run2);
    12 t2.Start();
    13
    14 Console.Read();
    15 }
    16
    17 static Semaphore sem = new Semaphore(3, 10, "cnblogs");
    18
    19 static void Run1()
    20 {
    21 sem.WaitOne();
    22
    23 Console.WriteLine("当前时间:{0} 大家好,我是Run1", DateTime.Now);
    24 }
    25
    26 static void Run2()
    27 {
    28 sem.WaitOne();
    29
    30 Console.WriteLine("当前时间:{0} 大家好,我是Run2", DateTime.Now);
    31 }
    32 }
    33 }

    是的,我设置了信号量是3个,所以只能有三个线程持有WaitOne,后续的线程只能苦苦的等待。

  • 相关阅读:
    《算法竞赛进阶指南》0x12 队列 POJ2259 Team Queue
    《算法竞赛进阶指南》0x11栈 单调栈求矩形面积 POJ2559
    《算法竞赛进阶指南》0x11 栈 求解中缀表达式
    19.职责链模式(Chain of Responsibility Pattern)
    16.观察者模式(Observer Pattern)
    17.解释器模式(Interpreter Pattern)
    15. 迭代器模式(Iterator Pattern)
    14.命令模式(Command Pattern)
    12.代理模式(Proxy Pattern)
    13.模板方法(Template Method)
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/2404181.html
Copyright © 2020-2023  润新知