1

考虑以下阻塞生产者和消费者线程的实现:

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,它等待两件事:

  1. 从消费者线程脉冲,和
  2. 用于重新获得锁

在上面的代码中,我在PulseandWait调用之间进行了消耗性的工作。

所以如果我这样写我的消费线程(等待前立即脉冲):

  Action consumer = () =>
  {
    lock (syncRoot)
      while (true)
      {
        products.ForEach(Console.WriteLine);

        products.Clear();
        Thread.Sleep(500);

        Monitor.Pulse(syncRoot);
        Monitor.Wait(syncRoot);
      }
  };

我没有注意到行为有任何变化。有什么指导方针吗?我们通常应该Pulse紧接在我们之前Wait还是在性能方面可能存在差异?

4

1 回答 1

0
  • Pulse then Wait 与 Wait then Pulse 几乎相同,因为它们在无限循环中运行。Pulse,Wait,Pulse,Wait 与 Wait,Pulse,Wait,Pulse 几乎相同

  • 通常,等待更改Wait的线程使用和执行更改的线程使用Pulse。一个线程可以同时做这两个,但一般做法取决于具体情况。

  • 给出的代码是在按住锁的同时睡觉。出于学习/模拟目的,这很好,但生产代码通常不应该这样做。您可以将超时传递给Wait这很好,并且在等待期间不持有锁。

  • 一般来说,Monitor 被认为是低级同步原语,建议使用更高级别的同步原语。了解像 Monitor 这样的低级原语很好,但普遍的看法是,对于几乎任何实际场景,都可以使用一些更不容易出错的更高级别的原语,不太可能隐藏一些棘手的比赛场景,并且更容易在某人身上阅读其他人的代码。

于 2014-01-30T20:18:06.717 回答