考虑以下阻塞生产者和消费者线程的实现:
static void Main(string[] args)
{
var syncRoot = new object();
var products = new List<int>();
Action producer = () => {
lock (syncRoot)
{
var counter = 0;
while (true)
{
products.Add(counter++);
Monitor.Pulse(syncRoot);
Monitor.Wait(syncRoot);
}
}};
Action consumer = () => {
lock (syncRoot)
while (true)
{
Monitor.Pulse(syncRoot);
products.ForEach(Console.WriteLine);
products.Clear();
Thread.Sleep(500);
Monitor.Wait(syncRoot);
}};
Task.Factory.StartNew(producer);
Task.Factory.StartNew(consumer);
Console.ReadLine();
}
假设当生成线程进入Monitor.Wait
时,它会等待两件事:
在上面的代码中,我在Pulse
和Wait
来电之间进行了消费工作。
所以,如果我写这样的消费线程(等待之前的脉冲):
Action consumer = () =>
{
lock (syncRoot)
while (true)
{
products.ForEach(Console.WriteLine);
products.Clear();
Thread.Sleep(500);
Monitor.Pulse(syncRoot);
Monitor.Wait(syncRoot);
}
};
我没有注意到行为有任何改变。那有什么指导方针吗?我们应该在Pulse
之前Wait
通常是{{1}}还是在性能方面存在差异?
答案 0 :(得分:0)
脉冲然后等待几乎与Wait然后Pulse相同,因为它们在无限循环中运行。脉冲,等待,脉冲,等待几乎与等待,脉冲,等待,脉冲
通常,等待更改内容的线程使用Wait
,执行更改的线程使用Pulse
。一个线程可以做到这两点,但一般的做法取决于具体情况。
给出的代码在按住锁定时正在睡觉。对于学习/模拟目的来说很好,但生产代码通常不应该这样做。您可以将超时传递给Wait
,这很好,并且在等待期间不会保持锁定。
一般来说,Monitor被认为是低级同步原语,建议使用更高级别的同步原语。理解像Monitor这样的低级原语是很好的,但一般的智慧是,对于几乎任何实际情况,一些更高级别的原语可用,不易出错,不太可能隐藏一些棘手的竞争场景,更容易在某人阅读别的代码。