C#:监控 - 等待,脉冲,PulseAll

49 c# multithreading

我有理解困难时期 Wait(),Pulse(),PulseAll().他们都会避免僵局吗?如果您解释如何使用它们,我将不胜感激?

Mar*_*ell 55

精简版:

lock(obj) {...}
Run Code Online (Sandbox Code Playgroud)

是(Monitor.Enter/ Monitor.Exit有异常处理等)的简写.如果没有其他人拥有锁,你可以得到它(并运行你的代码) - 否则你的线程被阻塞,直到获得锁(由另一个线程释放它).

当A:两个线程以不同的顺序锁定事物时,通常会发生死锁:

thread 1: lock(objA) { lock (objB) { ... } }
thread 2: lock(objB) { lock (objA) { ... } }
Run Code Online (Sandbox Code Playgroud)

(在这里,如果他们每获得第一锁,也不能永远获得第二,因为没有线程可以退出来释放自己的锁)

总是以相同的顺序锁定可以最小化这种情况; 并且您可以通过使用Monitor.TryEnter(而不是Monitor.Enter/ lock)并指定超时来恢复(到某种程度).

或者B:你可以在持有锁的情况下切换线程时使用winforms这样的东西:

lock(obj) { // on worker
    this.Invoke((MethodInvoker) delegate { // switch to UI
        lock(obj) { // oopsiee!
            ...
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

上面的僵局显而易见,但是当你有意大利面条代码时它并不那么明显; 可能的答案:不要在持有锁的情况下进行线程切换,或者使用BeginInvoke以便至少可以退出锁(让UI播放).


Wait/ Pulse/ PulseAll是不同的; 它们用于发信号.我在这个答案中使用这个信号,以便:

  • Dequeue:如果您在队列为空时尝试将数据出列,它会等待另一个线程添加数据,从而唤醒被阻塞的线程
  • Enqueue:如果您在队列已满时尝试将数据入队,它会等待另一个线程删除数据,从而唤醒被阻塞的线程

Pulse只唤醒一个线程 - 但我不够聪明,不能证明下一个线程始终是我想要的,所以我倾向于使用PulseAll,只是在继续之前重新验证条件; 举个例子:

        while (queue.Count >= maxSize)
        {
            Monitor.Wait(queue);
        }
Run Code Online (Sandbox Code Playgroud)

使用这种方法,我可以安全地添加其他含义Pulse,而不是我现有的代码假设"我醒了,因此有数据" - 这在(稍后)需要添加Close()方法的时候很方便.


gat*_*ich 43

使用Monitor.Wait和Monitor.Pulse的简单配方.它由工人,老板和他们用来沟通的电话组成:

object phone = new object();
Run Code Online (Sandbox Code Playgroud)

一个"工人"线程:

lock(phone) // Sort of "Turn the phone on while at work"
{
    while(true)
    {
        Monitor.Wait(phone); // Wait for a signal from the boss
        DoWork();
        Monitor.PulseAll(phone); // Signal boss we are done
    }
}
Run Code Online (Sandbox Code Playgroud)

一个"老板"线程:

PrepareWork();
lock(phone) // Grab the phone when I have something ready for the worker
{
    Monitor.PulseAll(phone); // Signal worker there is work to do
    Monitor.Wait(phone); // Wait for the work to be done
}
Run Code Online (Sandbox Code Playgroud)

更复杂的例子如下......

"有其他事可做的工人":

lock(phone)
{
    while(true)
    {
        if(Monitor.Wait(phone,1000)) // Wait for one second at most
        {
            DoWork();
            Monitor.PulseAll(phone); // Signal boss we are done
        }
        else
            DoSomethingElse();
    }
}
Run Code Online (Sandbox Code Playgroud)

一个"不耐烦的老板":

PrepareWork();
lock(phone)
{
    Monitor.PulseAll(phone); // Signal worker there is work to do
    if(Monitor.Wait(phone,1000)) // Wait for one second at most
        Console.Writeline("Good work!");
}
Run Code Online (Sandbox Code Playgroud)

  • 我不明白。Boss 和 Worker 怎么可能同时处于“电话”锁定状态? (2认同)
  • 你的第一个例子可能会死锁。假设老板首先获取锁,向所有等待线程(此时没有人)发送脉冲,然后释放锁并等待重新获取(Monitor.Wait)。一个工人抓住锁,然后释放它,等待永远不会到来的脉冲。当 worker 释放这个锁时,note boss 不能重新获取它,因为没有发送脉冲。对于任何获得锁的人,他们都必须在就绪队列中。要从等待队列到就绪队列,有人需要发送一个脉冲。 (2认同)

Vit*_*sky 10

不,他们不能保护你免受僵局.它们只是用于线程同步的更灵活的工具.这里有一个非常好的解释如何使用它们和非常重要的使用模式 - 没有这种模式你将打破所有的东西:http: //www.albahari.com/threading/part4.aspx