什么是监视器.脉冲和监视器.等待优势

本文关键字:监视器 等待 脉冲 什么 | 更新日期: 2023-09-27 18:01:13

我对并发编程有点陌生,并试图理解使用Monitor.Pulse和Monitor.Wait.的好处

MSDN的示例如下:

class MonitorSample
{
    const int MAX_LOOP_TIME = 1000;
    Queue   m_smplQueue;
    public MonitorSample()
    {
        m_smplQueue = new Queue(); 
    }
    public void FirstThread()
    {
        int counter = 0;
        lock(m_smplQueue)
        {
            while(counter < MAX_LOOP_TIME)
            {
                //Wait, if the queue is busy.
                Monitor.Wait(m_smplQueue);
                //Push one element.
                m_smplQueue.Enqueue(counter);
                //Release the waiting thread.
                Monitor.Pulse(m_smplQueue); 
                counter++;
            }
        }
    }
    public void SecondThread()
    {
        lock(m_smplQueue)
        {
            //Release the waiting thread.
            Monitor.Pulse(m_smplQueue);
            //Wait in the loop, while the queue is busy.
            //Exit on the time-out when the first thread stops. 
            while(Monitor.Wait(m_smplQueue,1000))
            {
                //Pop the first element.
                int counter = (int)m_smplQueue.Dequeue();
                //Print the first element.
                Console.WriteLine(counter.ToString());
                //Release the waiting thread.
                Monitor.Pulse(m_smplQueue);
            }
        }
    }
    //Return the number of queue elements.
    public int GetQueueCount()
    {
        return m_smplQueue.Count;
    }
    static void Main(string[] args)
    {
        //Create the MonitorSample object.
        MonitorSample test = new MonitorSample();           
        //Create the first thread.
        Thread tFirst = new Thread(new ThreadStart(test.FirstThread));
        //Create the second thread.
        Thread tSecond = new Thread(new ThreadStart(test.SecondThread));
        //Start threads.
        tFirst.Start();
        tSecond.Start();
        //wait to the end of the two threads
        tFirst.Join();
        tSecond.Join();         
        //Print the number of queue elements.
        Console.WriteLine("Queue Count = " + test.GetQueueCount().ToString());
    }
}

我看不出使用等待和脉冲代替的好处

    public void FirstThreadTwo()
    {
        int counter = 0;
        while (counter < MAX_LOOP_TIME)
        {
            lock (m_smplQueue)
            {
                m_smplQueue.Enqueue(counter);
                counter++;
            }
        }
    }
    public void SecondThreadTwo()
    {
        while (true)
        {
            lock (m_smplQueue)
            {
                    int counter = (int)m_smplQueue.Dequeue();
                    Console.WriteLine(counter.ToString());
            }
        }
    }

任何帮助都将不胜感激。感谢

什么是监视器.脉冲和监视器.等待优势

要描述"优势",一个关键问题是"超过什么?"。如果你的意思是"首选热循环",那么CPU利用率是显而易见的。如果你的意思是"优先于睡眠/重试循环",你可以得到更快的响应(Pulse不需要等待那么长时间)使用更低的CPU(你没有不必要地醒来2000次)。

不过,一般来说,人们的意思是"偏爱羊肉等"。

我倾向于广泛使用这些,甚至优先于互斥、重置事件等;原因:

  • 它们很简单,涵盖了我需要的大部分场景
  • 它们相对便宜,因为它们不需要一直到操作系统句柄(与操作系统拥有的Mutex等不同)
  • 我通常已经使用lock来处理同步,所以当我需要等待时,很可能我已经有了lock
  • 它实现了我的正常目标——允许两个线程以托管的方式相互发送完成信号
  • 我很少需要Mutex等的其他功能(例如跨进程)

您的代码片段中存在严重缺陷,当SecondThreadWo()尝试在空队列上调用Dequeue()时,它将严重失败。您可能是通过让FirstThreadWo()在使用者线程之前的几分之一秒执行,也可能是通过首先启动它来实现它。这是一个意外,在运行这些线程一段时间或用不同的机器负载启动它们后,它将停止工作。这可能会在相当长的一段时间内意外地无错误工作,很难诊断偶尔的故障。

没有办法编写一个锁定算法来阻止使用者,直到队列变成非空,只有lock语句。一个不断进出锁的繁忙循环可以工作,但它是一个非常糟糕的替代品。

编写这种代码最好留给线程大师,很难证明它在所有情况下都有效。不仅仅是缺少像这样的故障模式或线程竞争。但也适用于避免死锁、活锁和线程护送的算法。在.NET世界里,大师是Jeffrey Richter和Joe Duffy。他们早餐吃锁定设计,无论是在他们的书、博客还是杂志文章中。窃取他们的代码是意料之中的事,也是可以接受的。并在System.Collections.Concurrent命名空间中添加了部分内容,从而部分进入.NET框架。

正如您所猜测的,使用Monitor.Pulse/Wait是一种性能改进。获取锁是一项相对昂贵的操作。通过使用Monitor.Wait,您的线程将休眠,直到其他线程用"Monitor.Pulse"唤醒您的线程。

您将在TaskManager中看到差异,因为即使队列中没有任何内容,也会固定一个处理器核心。

PulseWait的优点是,它们可以用作所有其他同步机制的构建块,包括互斥、事件、屏障等。PulseWait可以完成BCL中任何其他同步机制都无法完成的一些事情。

所有有趣的事情都发生在Wait方法内部。Wait将退出关键部分,并通过将线程放入等待队列将其置于WaitSleepJoin状态。一旦调用了Pulse,则等待队列中的下一个线程移动到就绪队列。一旦线程切换到Running状态,它就会重新进入关键部分。用另一种方式重复这一点很重要。Wait将释放锁并以原子方式重新获取它。没有其他同步机制具有此功能。

设想这种情况的最好方法是尝试用其他策略复制这种行为,然后看看会出什么问题。让我们用ManualResetEvent来尝试这个例外,因为SetWaitOne方法看起来可能是相似的。我们的第一次尝试可能是这样的。

void FirstThread()
{
  lock (mre)
  {
    // Do stuff.
    mre.Set();
    // Do stuff.
  }
}
void SecondThread()
{
  lock (mre)
  {
    // Do stuff.
    while (!CheckSomeCondition())
    {
      mre.WaitOne();
    }
    // Do stuff.
  }
}

应该很容易看出代码可能会死锁。那么,如果我们尝试这种天真的解决方案,会发生什么呢?

void FirstThread()
{
  lock (mre)
  {
    // Do stuff.
    mre.Set();
    // Do stuff.
  }
}
void SecondThread()
{
  lock (mre)
  {
    // Do stuff.
  }
  while (!CheckSomeCondition())
  {
    mre.WaitOne();
  }
  lock (mre)
  {
    // Do stuff.
  }
}

你能看到这里出了什么问题吗?由于我们在检查等待条件后没有自动重新输入锁,因此另一个线程可能会进入并使条件无效。换句话说,另一个线程可以做一些事情,使CheckSomeCondition在重新获取下面的锁之前再次开始返回false。如果您的第二个代码块要求条件为true,那么这肯定会导致很多奇怪的问题。