带有Monitor.Wait和Monitor.Pulse的C#Producer-Consumer模式

时间:2014-01-30 19:28:07

标签: c# concurrency producer-consumer monitors

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

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. 用于重新获取锁定
  3. 在上面的代码中,我在PulseWait来电之间进行了消费工作。

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

      Action consumer = () =>
      {
        lock (syncRoot)
          while (true)
          {
            products.ForEach(Console.WriteLine);
    
            products.Clear();
            Thread.Sleep(500);
    
            Monitor.Pulse(syncRoot);
            Monitor.Wait(syncRoot);
          }
      };
    

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

1 个答案:

答案 0 :(得分:0)

  • 脉冲然后等待几乎与Wait然后Pulse相同,因为它们在无限循环中运行。脉冲,等待,脉冲,等待几乎与等待,脉冲,等待,脉冲

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

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

  • 一般来说,Monitor被认为是低级同步原语,建议使用更高级别的同步原语。理解像Monitor这样的低级原语是很好的,但一般的智慧是,对于几乎任何实际情况,一些更高级别的原语可用,不易出错,不太可能隐藏一些棘手的竞争场景,更容易在某人阅读别的代码。

相关问题