Каковы преимущества Monitor.Pulse и Monitor.Wait? - PullRequest
6 голосов
/ 05 июля 2011

Я немного новичок в параллельном программировании и пытаюсь понять преимущества использования Monitor.Pulse и Monitor.Wait.

Пример MSDN следующий:

class MonitorSample
{
    const int MAX_LOOP_TIME = 1000;
    Queue   m_smplQueue;

    public MonitorSample()
    {
        m_smplQueue = new Queue(); 
    }
    public void FirstThread()
    {
        int counter = 0;
        lock(m_smplQueue)
        {
            while(counter < MAX_LOOP_TIME)
            {
                //Wait, if the queue is busy.
                Monitor.Wait(m_smplQueue);
                //Push one element.
                m_smplQueue.Enqueue(counter);
                //Release the waiting thread.
                Monitor.Pulse(m_smplQueue); 

                counter++;
            }
        }
    }
    public void SecondThread()
    {
        lock(m_smplQueue)
        {
            //Release the waiting thread.
            Monitor.Pulse(m_smplQueue);
            //Wait in the loop, while the queue is busy.
            //Exit on the time-out when the first thread stops. 
            while(Monitor.Wait(m_smplQueue,1000))
            {
                //Pop the first element.
                int counter = (int)m_smplQueue.Dequeue();
                //Print the first element.
                Console.WriteLine(counter.ToString());
                //Release the waiting thread.
                Monitor.Pulse(m_smplQueue);
            }
        }
    }
    //Return the number of queue elements.
    public int GetQueueCount()
    {
        return m_smplQueue.Count;
    }

    static void Main(string[] args)
    {
        //Create the MonitorSample object.
        MonitorSample test = new MonitorSample();           
        //Create the first thread.
        Thread tFirst = new Thread(new ThreadStart(test.FirstThread));
        //Create the second thread.
        Thread tSecond = new Thread(new ThreadStart(test.SecondThread));
        //Start threads.
        tFirst.Start();
        tSecond.Start();
        //wait to the end of the two threads
        tFirst.Join();
        tSecond.Join();         
        //Print the number of queue elements.
        Console.WriteLine("Queue Count = " + test.GetQueueCount().ToString());
    }
}

ия не вижу преимущества использования Wait And Pulse вместо этого:

    public void FirstThreadTwo()
    {
        int counter = 0;
        while (counter < MAX_LOOP_TIME)
        {
            lock (m_smplQueue)
            {
                m_smplQueue.Enqueue(counter);
                counter++;
            }
        }
    }
    public void SecondThreadTwo()
    {
        while (true)
        {
            lock (m_smplQueue)
            {
                    int counter = (int)m_smplQueue.Dequeue();
                    Console.WriteLine(counter.ToString());
            }
        }
    }

Любая помощь наиболее ценится.Спасибо

Ответы [ 4 ]

12 голосов
/ 05 июля 2011

Чтобы описать «преимущества», ключевой вопрос - «над чем?».Если вы имеете в виду «в предпочтении к горячей петле», то загрузка ЦП очевидна.Если вы имеете в виду «в предпочтении циклу сна / повтора» - вы можете получить гораздо более быстрый ответ (Pulse не нужно ждать так долго) и используют более низкий процессор (вы не проснулись)2000 раз без необходимости).

Обычно, однако, люди имеют в виду «в предпочтении Mutex и т. Д.»события сброса и т.д .;причины:

  • они просты и охватывают большинство сценариев, в которых я нуждаюсь
  • они относительно дешевы, так как им не нужно переходить к дескрипторам ОС (в отличие отMutex и т. Д., Который принадлежит ОС)
  • Я обычно уже , использующий lock для обработки синхронизации, поэтому велика вероятность, что у меня уже есть lock, когда мне нужнождать чего-то
  • это достигает моей обычной цели - позволить двум потокам сигнализировать о завершении друг другу управляемым способом
  • Мне редко нужны другие функции Mutex и т. д. (такие как-процесс)
4 голосов
/ 05 июля 2011

В вашем фрагменте есть серьезный недостаток, SecondThreadTwo () потерпит неудачу, когда попытается вызвать Dequeue () в пустой очереди. Вы, вероятно, заставили его работать, выполнив FirstThreadTwo () за долю секунды до потока потребителя, возможно, запустив его первым. Это случайность, которая перестает работать после некоторого запуска этих потоков или запуска их с другой загрузкой машины. Это может случайно работать без ошибок в течение долгого времени, очень трудно диагностировать случайный сбой.

Нет способа написать алгоритм блокировки, который блокирует потребителя до тех пор, пока очередь не станет непустой с помощью только оператора блокировки. Цикл занятости, который постоянно входит и выходит из блокировки, работает, но является очень плохой заменой.

Написание такого рода кода лучше оставить гуру потоков, очень сложно доказать, что он работает во всех случаях. Не просто отсутствие режимов отказа, таких как этот, или многопоточности Но также общая пригодность алгоритма, который позволяет избежать взаимоблокировок, живых блокировок и потоков. В мире .NET гуру - Джеффри Рихтер и Джо Даффи. Они едят замки на завтрак, как в своих книгах, так и в блогах и журнальных статьях. Кража их кода ожидается и принята. И частично вошел в .NET Framework с дополнениями в пространстве имен System.Collections.Concurrent.

3 голосов
/ 05 июля 2011

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

Вы увидите разницу в TaskManager, потому что одно ядро ​​процессора будет привязано, даже если в очереди ничего нет.

0 голосов
/ 05 июля 2011

Преимущества Pulse и Wait в том, что они могут использоваться в качестве строительных блоков для всех других механизмов синхронизации, включая мьютексы, события, барьеры и т. Д. Есть вещи, которые можно сделать с помощью Pulse и * 1004.* это невозможно сделать с помощью любого другого механизма синхронизации в BCL.

Все интересное происходит внутри метода Wait.Wait выйдет из критической секции и переведет поток в состояние WaitSleepJoin, поместив его в очередь ожидания.После вызова Pulse следующий поток в очереди ожидания перемещается в очередь готовности.Как только поток переключается в состояние Running, он снова входит в критическую секцию.Это важно повторить по-другому.Wait откроет замок и снова его захватит атомным способом .Ни один другой механизм синхронизации не имеет этой функции.

Лучший способ представить это - попытаться повторить поведение с другой стратегией, а затем посмотреть, что может пойти не так.Давайте попробуем этот эксрайз с ManualResetEvent, поскольку методы Set и WaitOne кажутся такими, как будто они могут быть аналогичными.Наша первая попытка может выглядеть следующим образом.

void FirstThread()
{
  lock (mre)
  {
    // Do stuff.
    mre.Set();
    // Do stuff.
  }
}

void SecondThread()
{
  lock (mre)
  {
    // Do stuff.
    while (!CheckSomeCondition())
    {
      mre.WaitOne();
    }
    // Do stuff.
  }
}

Должно быть легко увидеть, что код может зайти в тупик.Так что же произойдет, если мы попробуем это наивное исправление?

void FirstThread()
{
  lock (mre)
  {
    // Do stuff.
    mre.Set();
    // Do stuff.
  }
}

void SecondThread()
{
  lock (mre)
  {
    // Do stuff.
  }
  while (!CheckSomeCondition())
  {
    mre.WaitOne();
  }
  lock (mre)
  {
    // Do stuff.
  }
}

Можете ли вы увидеть, что здесь может пойти не так?Поскольку мы не вводили блокировку заново после проверки условия ожидания, другой поток мог войти и сделать условие недействительным.Другими словами, другой поток может сделать что-то, что заставит CheckSomeCondition снова начать возвращать false до того, как будет восстановлена ​​следующая блокировка.Это определенно может вызвать много странных проблем, если ваш второй блок кода требует, чтобы условие было true.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...