Шаблон C# Producer-Consumer с Monitor.Wait и Monitor.Pulse

Рассмотрим следующую реализацию блокировки потоков производителя и потребителя:

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. для повторной блокировки

В приведенном выше коде я делаю мою работу между Pulse а также Wait звонки.

Так что, если я напишу свою цепочку потребления следующим образом (Pulse непосредственно перед ожиданием):

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

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

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

Я не заметил каких-либо изменений в поведении. Есть ли какие-либо рекомендации к этому? Должны ли мы вообще Pulse непосредственно перед тем, как мы Wait или есть разница в производительности?

1 ответ

  • Импульс затем Ожидание почти идентичен Ожиданию затем Импульсу, так как они работают в бесконечном цикле. Pulse,Wait,Pulse,Wait почти такой же, как Wait,Pulse,Wait,Pulse

  • Обычно поток, ожидающий изменения, использует Wait и поток делает изменения использует Pulse, Поток может делать и то и другое, но общая практика зависит от обстоятельств.

  • Данный код спит, удерживая замок. Для обучения / моделирования это нормально, но производственный код, как правило, не должен этого делать. Вы можете передать тайм-аут Wait что нормально и не держит блокировку во время ожидания.

  • Вообще говоря, Monitor считается низкоуровневым примитивом синхронизации, поэтому рекомендуется использовать вместо него высокоуровневые примитивы синхронизации. Хорошо понимать низкоуровневые примитивы, такие как Monitor, но общая мудрость заключается в том, что практически для любого практического сценария доступен некоторый примитив более высокого уровня, который менее подвержен ошибкам, менее вероятен, чтобы скрыть какой-то сложный сценарий гонки, и легче читается в ком-то чужой код.

Другие вопросы по тегам