zoukankan      html  css  js  c++  java
  • 深入浅出多线程系列之十:Wait 和Pulse

    Signaling with Wait and Pulse(等待和暂停的信号)

     

    早期谈论过等待事件句柄(调用Wait的线程在没有收到另一个线程的通知前会一直阻塞)

    Monitor借助它的静态方法WaitPulsePulseAll提供了一个更给力的信号构造,使用这些方法和lock语句,你可以自己实现AutoResetEventManualResetEventSemaphore。甚至WaitHandleWaitAllWaitAny方法了。

    怎样使用Wait Pulse ?

    1:定义一个同步对象,例如:

      Readonly object _locker=new object();

    2:定义自己的阻塞条件中的字段。

      bool _go 或者 int _semaphoreCount;

    3:当你想要阻塞的时候,包含下面的代码

      lock(_locker)

             while(<阻塞条件 >) //比如while (_go ==false)

                       Monitor.Wait(_locker);    //满足阻塞条件,开始阻塞。

    4:当想要改变阻塞条件的时候,包含下面的代码:

         lock(_locker)

    {

        //<更改阻塞条件中的字段>,比如_go=true;

             Monitor.Pulse(_locker); //或者: Monitor.PulseAll(_locker); //通知等待队列中的线程锁定对象状态的更改。

    }

    这个模式可以让你随时随地等待线程。下面是一个例子,worker线程在_go 字段变成true之前会一直等待。 

            static readonly object _locker = new object();
            
    static bool _go;

            
    internal static void Main()
            {
                
    new Thread(Work).Start(); //新线程会被阻塞,因为_go == false
                Console.ReadLine(); //等待用户输入

                
    lock (_locker)
                {
                    _go 
    = true; //改变阻塞条件
                    Monitor.Pulse(_locker); 
    //通知等待的队列。
                }
            }

            
    static void Work()
            {
                
    lock (_locker)
                {
                    
    while (!_go) //只要_go字段是false,就等待。
                        Monitor.Wait(_locker); //在等待的时候,锁已经被释放了。
                }

                Console.WriteLine(
    "被唤醒了");
            }

    为了线程安全,确保所有共享的字段在读取的时候都加锁了。 

    Work方法会一直阻塞,等待_go字段变成trueMonitor.Wait方法按顺序的做了以下的操作。

    1:释放锁_locker;

    2:阻塞锁,直到_locker ”pulsed”

    3:重新在_locker 上获取锁,如果锁已经被其他线程获得,那么线程开始阻塞,直到锁变得可用为止。

    lock(_locker)
    {
        While(
    !_go)
            Monitor.Wait(_locker); 
    //释放锁
        
    //已经重新获取了锁。
    }

    如果我们抛弃该模式,例如移除while循环。_go字段和ReadLine方法等:

            static object _locker = new object();

            
    internal static void Main()
            {
                
    new Thread(Work).Start();
                
    lock (_locker) Monitor.Pulse(_locker);
            }

            
    static void Work()
            {
                
    lock (_locker) Monitor.Wait(_locker);
                Console.WriteLine(
    "被唤醒了");
            }
     

    那么程序运行的结果又如何呢?

    实际上输出是不确定的,有可能你不能显示“被唤醒了”。

    主要原因是主线程和Work线程之间存在着竞争关系,如果Wait方法先执行,那么可以正常显示,但是如果Pulse方法先执行,pulse就会丢失,worker线程就会永远的等待。这种行为和AutoResetEvent不同,AutoResetEventSet方法有一种记忆的效果,所以即使它在WaitOne方法前调用,它仍然有效。

    但是Pulse没有记忆功能,因为你希望自己实现记忆功能,就像我们之前使用_go 标志一样,

    这就是为什么WaitPulse是通用的原因:使用一个boolean 标志,我们可以实现AutoResetEvent的功能,使用一个integer标志,我们可以实现 CountdownEvent,Semaphore.使用更复杂的结构,我么可以写一些更复杂的构造,例如 生产/消费者队列。

    下篇文章会介绍生产/消费者队列。

    参考资料:

    http://www.albahari.com/threading/

    CLR Via C# 3.0

     

  • 相关阅读:
    学习java第20天
    学习java第19天
    学习java第18天
    学习java第17天
    学习java第16天
    java架构师学习路线-Web分布式开发框架概述
    java架构师学习路线-并发编程的概念
    java架构师学习路线-Java系统中的微服务框架
    java架构师学习路线-HashMap的知识点总结归纳
    java架构师学习路线-Java并发编程的五种状态和两种创建方式
  • 原文地址:https://www.cnblogs.com/LoveJenny/p/2060777.html
Copyright © 2011-2022 走看看