2013-11-03 74 views
4

下面是一个最小的代码说明了问题:竞争条件?

StringBuilder input = new StringBuilder(); 

void ToUpper() 
{ 
    lock (input) 
    { 
     while (true) 
     { 
      Monitor.Wait(input); 

      Console.WriteLine(input.ToString().ToUpper()); 
     } 
    } 
} 

public void Run() 
{ 
    new Thread(ToUpper) { IsBackground = true }.Start(); 

    // "Avoid" the initial race 
    Thread.Sleep(100); 

    while (true) 
    { 
     lock (input) 
     { 
      input.Clear(); 
      input.Append(Console.ReadLine()); 
      Monitor.Pulse(input); 
     } 
     // Thread.Sleep(1); 
    } 
} 

忽略众所周知的初始竞争条件,我对脉冲的行为感到惊讶和等待。

这是我所期待的:

  • 的“ToUpper的”线程调用Wait =>它推到显示器的等待队列
  • 主线程调用Pulse =>在“ToUpper的”线程“移动“从等待队列到就绪队列,以便立即获取锁
  • 主线程在退出lock声明范围时退出监视器
  • ”ToUpper“线程获取了锁并处理的SSE输入,而主线程的锁的所有权重新注册

一次出两个“ToUpper的”线程不处理输入,而是在主线程立即执行它的处理

这里是我的假设:

  • Pulse不“动”的“ToUpper的”线程立刻让就绪队列中处于空
  • 主线程释放锁,循环,同样需要锁定
  • 因为没有人就绪队列中否则取得所有权
  • 有时后来为推进“ToUpper的”线程的请求被执行,并最终推入就绪队列
  • 主线程Pulse第二时间没有和释放锁
  • 它循环,试图重新获取锁,但“ToUpper的”线程已经存在
  • 这一次的“ToUpper的”线程获取锁和过程输入
  • 做一次它睡觉再等待下一个信号
  • 主线程获取锁

要检查这个假设我已经迫使主线程不那么渴望并让对方线程工作,通过添加Thread.Sleep(1) =>在这种情况下,所有的“工作正常”。

因此,一切都归结为Pulse可能不从等待队列立即推线程就绪队列的行为。

是否真正的问题来自于这个可能的竞争或有另一种微妙我失踪?

回答

4

你有关添加Thread.Sleep(1)句话是真的答案。该Pulse方法并不能保证任何事情等待的对象 - 这是刚刚发布,以从中收益作为普通螺纹ready queue。对Pulse的呼叫没有任何跟踪。因此,在调用Pulse后,应用程序将像普通的双线程应用程序那样工作,并且在就绪队列中有两个线程--线程线程Run。所以如果没有Thread.Sleep(1)它可能会发生(没有它,我猜也可能,只是不太可能)Run线程首先获得锁。

从我所提供的第二个链接另一个重要的一句话:

Monitor.Pulse的一个重要特点是,它执行异步的,这意味着它本身不阻止或以任何方式暂停。

对于这种情况AutoResetEvent类似乎更合适。此外,在second link中,您可以找到生产者 - 消费者场景示例,其中WaitPulse

+0

感谢您的回答Bartosz。 MSDN文档不是很清楚,这可能意味着该线程立即被移动,显然不是这种情况。在你的第二个链接中没有这样的问题,因为从集合中推送和弹出的两个操作从不重叠,而在我的情况下,第二个线程依赖于被第一个操作覆盖的状态。我试图反汇编Monitor类,但它转发到外部函数ObjWait/ObjPulse。你的报价证实了我的假设。 – Pragmateek

+0

来自同一篇文章:“此外,当通知者发出并释放其锁定时,不能保证合格的服务员会立即投入生活。可能会有一小段延迟,由线程调度程序自行决定,在此期间这意味着脉冲发生器无法知道服务员是否或何时恢复 - 除非您专门编写了某些内容(例如使用另一个标志和另一个相反的Wait,Pulse)。“和“依靠服务员的及时行动,没有定制的确认机制会被Wait和Pulse搞乱,你会失败的!” – Pragmateek

+0

最后一个是一个很好的总结。 :) – Pragmateek