下面是一个最小的代码说明了问题:竞争条件?
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
可能不从等待队列立即推线程就绪队列的行为。
是否真正的问题来自于这个可能的竞争或有另一种微妙我失踪?
感谢您的回答Bartosz。 MSDN文档不是很清楚,这可能意味着该线程立即被移动,显然不是这种情况。在你的第二个链接中没有这样的问题,因为从集合中推送和弹出的两个操作从不重叠,而在我的情况下,第二个线程依赖于被第一个操作覆盖的状态。我试图反汇编Monitor类,但它转发到外部函数ObjWait/ObjPulse。你的报价证实了我的假设。 – Pragmateek
来自同一篇文章:“此外,当通知者发出并释放其锁定时,不能保证合格的服务员会立即投入生活。可能会有一小段延迟,由线程调度程序自行决定,在此期间这意味着脉冲发生器无法知道服务员是否或何时恢复 - 除非您专门编写了某些内容(例如使用另一个标志和另一个相反的Wait,Pulse)。“和“依靠服务员的及时行动,没有定制的确认机制会被Wait和Pulse搞乱,你会失败的!” – Pragmateek
最后一个是一个很好的总结。 :) – Pragmateek