繁体   English   中英

Monitor.Pulse 和 Monitor.Wait 的优势是什么?

[英]What are Monitor.Pulse And Monitor.Wait advantages?

我对并发编程有点陌生,并试图了解使用 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());
    }
}

而且我看不到使用 Wait And Pulse 而不是这个的好处:

    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 等”。

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

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

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

没有办法编写一个锁定算法来阻止消费者,直到队列变为非空,仅使用锁定语句。 一个不断进出锁的繁忙循环是有效的,但它是一个非常糟糕的替代品。

编写这种代码最好留给线程专家,很难证明它在所有情况下都有效。 不仅仅是没有像这样的故障模式或线程竞赛。 而且还具有避免死锁、活锁和线程护航的算法的一般适用性。 在 .NET 世界中,大师是 Jeffrey Richter 和 Joe Duffy。 他们在早餐时吃锁设计,无论是在他们的书中,还是在他们的博客和杂志文章中。 窃取他们的代码是可以预料和接受的。 并通过 System.Collections.Concurrent 命名空间中的添加部分进入 .NET 框架。

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

您将在 TaskManager 中看到不同之处,因为即使队列中没有任何内容,一个处理器内核也会被锁定。

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

所有有趣的事情都发生在Wait方法中。 Wait将退出临界区并将线程放入WaitSleepJoin state 中,方法是将其放入等待队列中。 调用Pulse后,等待队列中的下一个线程将移动到就绪队列。 一旦线程切换到Running state,它就会重新进入临界区。 以另一种方式重复这一点很重要。 Wait将释放锁并以原子方式重新获取它。 没有其他同步机制具有此功能。

设想这一点的最佳方法是尝试使用其他策略复制该行为,然后查看 go 会出现什么错误。 让我们用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.
  }
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM