C# 监视器之间的竞争状态。脉冲和监视器。等待?

C# 监视器之间的竞争状态。脉冲和监视器。等待?,c#,.net,multithreading,race-condition,C#,.net,Multithreading,Race Condition,下面是一段说明此问题的简短代码: StringBuilder input = new StringBuilder(); void ToUpper() { lock (input) { while (true) { Monitor.Wait(input); Console.WriteLine(input.ToString().ToUpper()); } } } public

下面是一段说明此问题的简短代码:

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);
    }
}
忽略众所周知的初始竞争条件,我对Pulse和Wait的行为感到惊讶

以下是我的预期:

  • “ToUpper”线程调用
    Wait
    =>它被推送到监视器的等待队列中
  • 主线程调用
    Pulse
    =>将“ToUpper”线程从等待队列“移动”到准备队列,以便立即获取锁
  • 当退出
    语句范围时,主线程将退出监视器
  • “ToUpper”线程获取锁并处理输入,同时主线程再次注册锁的所有权
但是两次中有一次“ToUpper”线程不处理输入,而是主线程立即执行其处理

以下是我的假设:

  • Pulse
    不会立即“移动”ToUpper线程,以便就绪队列保持为空
  • 主线程释放锁,循环,再次需要锁
  • 由于就绪队列中没有其他人,因此它拥有所有权
  • 有时,稍后会执行提升“ToUpper”线程的请求,并最终将其推入就绪队列
  • 主线程无需第二次脉冲
    并释放锁
  • 它循环,试图重新获取锁,但“ToUpper”线程已经存在
  • 这次“ToUpper”线程获取锁并处理输入
  • 一旦完成,它会再次休眠,等待下一个信号
  • 主线程获取锁
为了验证这个假设,我通过添加
thread.Sleep(1)
=>在本例中,所有线程都“按预期工作”,迫使主线程不那么急切,让其他线程工作

因此,这一切归结为
Pulse
的行为,它可能不会立即将线程从等待队列推送到就绪队列


这个问题真的来自于这场可能的比赛,还是我缺少了另一个微妙之处?

你关于添加
线程的评论。睡眠(1)
才是真正的答案。该方法不保证等待对象的任何东西——它只是被释放到一个普通线程,并从中继续执行。调用
脉冲
时没有任何痕迹。因此,在调用
Pulse
后,应用程序的行为与普通的双线程应用程序一样,就绪队列中有两个线程--
touper
thread
Run
线程。因此,如果没有
Thread.Sleep(1)
的话,
Run
线程可能首先获得锁

我提供的第二个链接中的另一个重要评论:

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


对于这个场景,类似乎更合适。另外,在中,您可以找到一个生产者-消费者场景的示例,其中包含
Wait
Pulse

感谢您的回答Bartosz。MSDN文档并不十分清楚,这可能意味着线程会立即移动,但事实显然并非如此。在第二个链接中没有这样的问题,因为两个操作(从集合中推送和弹出)从不重叠,而在我的情况下,第二个线程依赖于第一个线程覆盖的状态。我试图反汇编Monitor类,但它将转发给外部函数ObjWait/ObjPulse。你的报价证实了我的假设。同一篇文章:“此外,当通知程序发出脉冲并释放锁时,无法保证一个合格的服务员会立即生效。由线程调度程序决定,可能会有一个小的延迟,在此期间,两个线程都没有锁。这意味着脉冲发生器无法知道服务员是否或何时恢复——除非您专门编写了代码(例如,使用另一个标志和另一个倒数,Wait and Pulse)。“依赖没有自定义确认机制的服务员的及时操作将被视为对Wait and Pulse的“干扰”。你会输的!“@Pragmateek是的,的确,我觉得整篇文章都很有趣。所以也谢谢你的问题,这让我找到了它:)