我很难理解Wait()
,Pulse()
,PulseAll()
。他们都会避免僵局吗?如果您解释如何使用它们,我将不胜感激?
答案 0 :(得分:52)
简短版本:
lock(obj) {...}
是Monitor.Enter
/ Monitor.Exit
的简写(有异常处理等)。如果没有其他人拥有锁,你可以得到它(并运行你的代码) - 否则你的线程被阻塞,直到获得锁(由另一个线程释放它)。
当A:两个线程以不同的顺序锁定事物时,通常会发生死锁:
thread 1: lock(objA) { lock (objB) { ... } }
thread 2: lock(objB) { lock (objA) { ... } }
(这里,如果他们各自获得第一个锁,那么永远不会获得第二个锁,因为两个线程都不能退出以释放锁)
这种情况可以通过始终以相同的顺序锁定来最小化;并且您可以使用Monitor.TryEnter
(而不是Monitor.Enter
/ lock
)并指定超时来恢复(在一定程度上)。
或B:你可以在握住锁定时进行线程切换时使用winforms这样的东西:
lock(obj) { // on worker
this.Invoke((MethodInvoker) delegate { // switch to UI
lock(obj) { // oopsiee!
...
}
});
}
上面的僵局显而易见,但是当你有意大利面条代码时它并不那么明显;可能的答案:不要在按住锁定时进行线程切换,或使用BeginInvoke
以便至少可以退出锁定(让UI播放)。
Wait
/ Pulse
/ PulseAll
不同;它们用于发信号。我使用此in this answer发出信号,以便:
Dequeue
:如果您在队列为空时尝试将数据出列,它会等待另一个线程添加数据,从而唤醒被阻塞的线程Enqueue
:如果您在队列已满时尝试将数据入队,它会等待另一个线程删除数据,从而唤醒被阻塞的线程 Pulse
只唤醒一个线程 - 但我不够聪明,不能证明下一个线程总是我想要的,所以我倾向于使用{{1}在继续之前简单地重新验证条件;举个例子:
PulseAll
通过这种方法,我可以安全地添加 while (queue.Count >= maxSize)
{
Monitor.Wait(queue);
}
的其他含义,而我现有的代码假设“我醒了,因此有数据” - 这在以后需要的时候(在同一个例子中)很方便添加Pulse
方法。
答案 1 :(得分:39)
使用Monitor.Wait和Monitor.Pulse的简单配方。它由工人,老板和他们用来沟通的电话组成:
object phone = new object();
“工人”主题:
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
}
}
“老板”主题:
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
}
更复杂的例子如下......
“有其他事可做的工人”:
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();
}
}
“不耐烦的老板”:
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!");
}
答案 2 :(得分:9)
不,它们不能保护您免受死锁。它们只是用于线程同步的更灵活的工具。这是一个非常好的解释如何使用它们和非常重要的使用模式 - 没有这种模式你将打破所有的东西: http://www.albahari.com/threading/part4.aspx
答案 3 :(得分:1)
它们是线程之间同步和信令的工具。因此,它们不会阻止死锁,但如果使用正确,它们可用于在线程之间进行同步和通信。
不幸的是,编写正确的多线程代码所需的大部分工作目前是开发人员在C#(以及许多其他语言)中的责任。看看F#,Haskell和Clojure如何处理这种完全不同的方法。
答案 4 :(得分:1)
不幸的是,Wait(),Pulse()或PulseAll()的 none 具有您希望的神奇属性 - 这就是使用此 API将自动避免死锁。
考虑以下代码
object incomingMessages = new object(); //signal object
LoopOnMessages()
{
lock(incomingMessages)
{
Monitor.Wait(incomingMessages);
}
if (canGrabMessage()) handleMessage();
// loop
}
ReceiveMessagesAndSignalWaiters()
{
awaitMessages();
copyMessagesToReadyArea();
lock(incomingMessages) {
Monitor.PulseAll(incomingMessages); //or Monitor.Pulse
}
awaitReadyAreaHasFreeSpace();
}
这段代码会死锁!也许不是今天,也许不是明天。最有可能的情况是,当您的代码处于压力之下时,因为它突然变得流行或重要,并且您被要求解决紧急问题。
为什么?
最终会发生以下情况:
这个特殊的例子假设生产者线程永远不会再次调用PulseAll(),因为它没有更多的空间来放入消息。但是这个代码上有许多损坏的变体。人们会尝试通过更改诸如将Monitor.Wait();
转换为
if (!canGrabMessage()) Monitor.Wait(incomingMessages);
不幸的是,这仍然不足以修复它。要修复它,还需要更改调用Monitor.PulseAll()
的锁定范围:
LoopOnMessages()
{
lock(incomingMessages)
{
if (!canGrabMessage()) Monitor.Wait(incomingMessages);
}
if (canGrabMessage()) handleMessage();
// loop
}
ReceiveMessagesAndSignalWaiters()
{
awaitMessagesArrive();
lock(incomingMessages)
{
copyMessagesToReadyArea();
Monitor.PulseAll(incomingMessages); //or Monitor.Pulse
}
awaitReadyAreaHasFreeSpace();
}
关键是在固定代码中,锁限制了可能的事件序列:
消费者线程完成其工作和循环
该线程获取锁
由于锁定,现在 :
一个。消息尚未到达就绪区域,它通过调用Wait()释放锁定,然后消息接收器线程可以获取锁定并将更多消息复制到就绪区域,或的
湾消息已在就绪区域中已到达,并且它接收调用Wait()的消息INSTEAD OF。 (当它做出这个决定时,消息接收者线程不可能例如获取锁并将更多消息复制到就绪区域。)
结果现在永远不会出现原始代码的问题: 3.当调用PulseEvent()时没有消费者被唤醒,因为没有人等待
现在请注意,在此代码中,必须完全正确地使用锁定范围。 (如果,我确实做对了!)
此外,由于您必须使用lock
(或Monitor.Enter()
等)才能以无死锁的方式使用Monitor.PulseAll()
或Monitor.Wait()
,不得不担心由于锁定而发生其他死锁的可能性。
底线:这些API也容易搞砸和死锁,即非常危险
答案 5 :(得分:1)
总共把我扔到这里的东西是Pulse
只是给Wait
中的一个主题“抬头”。等待线程将不会继续,直到执行Pulse
的线程放弃锁并且等待线程成功获胜。
lock(phone) // Grab the phone
{
Monitor.PulseAll(phone); // Signal worker
Monitor.Wait(phone); // ****** The lock on phone has been given up! ******
}
或
lock(phone) // Grab the phone when I have something ready for the worker
{
Monitor.PulseAll(phone); // Signal worker there is work to do
DoMoreWork();
} // ****** The lock on phone has been given up! ******
在这两种情况下,直到“手机上的锁定已被放弃”,另一个线程才能获得它。
可能有其他线程在等待来自Monitor.Wait(phone)
或lock(phone)
的锁定。只有赢得锁定的人才能继续。
答案 6 :(得分:0)
这是显示器使用的简单示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp4
{
class Program
{
public static int[] X = new int[30];
static readonly object _object = new object();
public static int count=0;
public static void PutNumbers(int numbersS, int numbersE)
{
for (int i = numbersS; i < numbersE; i++)
{
Monitor.Enter(_object);
try
{
if(count<30)
{
X[count] = i;
count++;
Console.WriteLine("Punt in " + count + "nd: "+i);
Monitor.Pulse(_object);
}
else
{
Monitor.Wait(_object);
}
}
finally
{
Monitor.Exit(_object);
}
}
}
public static void RemoveNumbers(int numbersS)
{
for (int i = 0; i < numbersS; i++)
{
Monitor.Enter(_object);
try
{
if (count > 0)
{
X[count] = 0;
int x = count;
count--;
Console.WriteLine("Removed " + x + " element");
Monitor.Pulse(_object);
}
else
{
Monitor.Wait(_object);
}
}
finally
{
Monitor.Exit(_object);
}
}
}
static void Main(string[] args)
{
Thread W1 = new Thread(() => PutNumbers(10,50));
Thread W2 = new Thread(() => PutNumbers(1, 10));
Thread R1 = new Thread(() => RemoveNumbers(30));
Thread R2 = new Thread(() => RemoveNumbers(20));
W1.Start();
R1.Start();
W2.Start();
R2.Start();
W1.Join();
R1.Join();
W2.Join();
R2.Join();
}
}
}