Monitor vs WaitHandle基于线程同步

时间:2009-08-31 01:03:26

标签: c# multithreading

在阅读this article后,我认为最好使用Monitor / Lock进行线程同步,因为它不使用本机资源

具体引用(来自文章第5页):

  

Monitor.Wait / Pulse不是在一个线程中等待某事发生并且告诉该线程它在另一个线程中发生的唯一方法。 Win32程序员长期以来一直在使用各种其他机制,这些机制由AutoResetEvent,ManualResetEvent和Mutex类公开,所有这些都派生自WaitHandle。所有这些类都在System.Threading命名空间中。 (Win32 Semaphore机制在.NET 1.1中没有托管包装。它存在于.NET 2.0中,但是如果你需要在那之前使用它,你可以使用P / Invoke自己包装它,或者编写你自己的计数信号量上课。)

     

有些人可能会惊讶地发现使用这些类可能比使用各种Monitor方法慢得多。我相信这是因为将托管代码“输出”到本机Win32调用并再次返回“in”与Monitor提供的完全托管视图相比是昂贵的。读者还解释了监视器是在用户模式下实现的,而使用等待句柄需要切换到内核模式,这是相当昂贵的。

但是,自从发现SO并阅读一些问题/答案后,我开始怀疑我对何时使用每个问题的理解。似乎许多人建议在Monitor.Wait / Pulse可以使用的情况下使用Auto / ManualResetEvent。任何人都可以向我解释基于WaitHandle的同步应该在Monitor上使用吗?

由于

3 个答案:

答案 0 :(得分:56)

Monitor.Pulse/Wait的问题是信号可能会丢失。

例如:

var signal = new ManualResetEvent(false);

// Thread 1
signal.WaitOne();

// Thread 2
signal.Set();

无论执行不同线程中的两个语句,这都将始终有效。它也是一个非常干净的抽象,并且非常清楚地表达了你的意图。

现在看一下使用监视器的相同示例:

var signal = new object();

// Thread 1
lock (signal)
{
    Monitor.Wait(signal);
}

// Thread 2
lock (signal)
{
    Monitor.Pulse(signal);
}

如果在Pulse之前执行Pulse,则信号(Wait)将会丢失。

要解决此问题,您需要以下内容:

var signal = new object();
var signalSet = false;

// Thread 1
lock (signal)
{
    while (!signalSet)
    {
        Monitor.Wait(signal);
    }
}

// Thread 2
lock (signal)
{
    signalSet = true;
    Monitor.Pulse(signal);
}

这有效,可能性能更高,重量更轻,但可读性更差。这就是令人头疼的并发症。

  • 这段代码真的有效吗?
  • 在每个角落的情况下?
  • 有两个以上的主题? (提示:它没有)
  • 你如何进行单元测试?

稳固,可靠,可读的抽象通常比原始性能更好。

此外,WaitHandles提供了一些很好的东西,比如等待设置一组句柄等等。用监视器实现这一点会使头痛更加严重......


经验法则:

  • 使用监控lock)确保对共享资源的独占访问
  • 使用 WaitHandles (手动/ AutoResetEvent / Semaphore)在线程之间发送信号

答案 1 :(得分:3)

我想我有第三个子弹的一个很好的例子(虽然这个线程有点旧,但它可能对某人有帮助。)

我有一些代码,其中线程A接收网络消息,将它们排队,然后脉冲线程B.线程B锁定,使任何消息出列,解锁队列,然后处理消息。

问题到来了,当线程B处理,而不是等待时,如果A获得新的网络消息,则排队并发出脉冲......好吧,B没有等待,所以脉冲就会蒸发。如果B然后完成它正在做的事情并点击Monitor.Wait(),那么最近添加的消息将一直挂起,直到另一条消息到达并且接收到脉冲。

请注意,这个问题并没有真正浮出水面,因为最初我的整个循环是这样的:

while (keepgoing)
  { 
  lock (messageQueue)
      {
      while (messageQueue.Count > 0)
          ProcessMessages(messageQueue.DeQueue());

      Monitor.Wait(messageQueue);
      }
  }

这个问题没有出现(好吧,关机时很少有奇怪,所以我对这段代码有点怀疑),直到我认为(可能长时间运行)消息处理不应该将队列锁定为它没有理由。所以我把它更改为出列消息,离开锁定,然后进行处理。然后好像我开始丢失消息,或者他们只会在第二次事件发生后才到达......

答案 2 :(得分:-1)

对于@Will Gore的情况,在调用Monitor.Wait之前,总是继续处理队列直到它为空是一个好习惯。 E.g:

while (keepgoing)
{ 
  List<Message> nextMsgs = new List<Message>();
  lock (messageQueue)
  {
    while (messageQueue.Count == 0)
    {
        try
        {
            Monitor.Wait(messageQueue);
        }
        catch(ThreadInterruptedException)
        {
            //...
        }
    }
    while (messageQueue.Count > 0)
        nextMsgs.Add(messageQueue.DeQueue());
  }
  if(nextMsgs.Count > 0)
    ProcessMessages(nextMsgs);
}

这应该解决你遇到的问题,并减少锁定时间(非常重要!)。