Объяснение текста о потоках в «C # 3.0 в двух словах» - PullRequest
7 голосов
/ 24 февраля 2010

Читая C # 3.0 в двух словах от Джозеф и Бен Албахари , я наткнулся на следующий абзац (стр. 673, первый абзац в разделе под названием " Сигнализация с ожиданием и импульсом")

"Класс Monitor предоставляет другую сигнальную конструкцию с помощью двух статических методов: Wait и Pulse . Принцип заключается в том, что вы сами пишете логику сигнализации с использованием пользовательских флаги и поля (заключенные в операторы lock ), а затем вводят команды Wait и Pulse для уменьшения вращения ЦП. Преимущество этого низкоуровневого подхода заключается в том, что используя Wait , Pulse и оператор lock , вы можете достичь функциональности AutoResetEvent , ManualResetEvent и семафор , а также статические методы WaitHandle WaitAll и WaitAny . Кроме того, Wait и Pulse может быть поддается в ситуациях, когда все ручки ожидания скупой вызов. "

У меня вопрос: какова правильная интерпретация последнего предложения?

  • Ситуация с приличным / большим количеством дескрипторов ожидания, когда WaitOne () вызывается лишь изредка для любого конкретного дескриптора ожидания.
  • Ситуация с приличным / большим количеством дескрипторов ожидания, когда редко более одного потока имеют тенденцию блокировать какой-либо конкретный дескриптор ожидания.
  • Некоторая другая интерпретация.

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

Спасибо!

Редактировать: я нашел текст онлайн здесь

1 Ответ

5 голосов
/ 24 февраля 2010

Это говорит о том, что в некоторых ситуациях Wait и Pulse предоставляют более простое решение, чем дескрипторы ожидания. В общем случае это происходит где:

  • Официант, а не уведомитель, решает, когда разблокировать
  • Условие блокировки включает в себя больше, чем простой флаг (возможно, несколько переменных)

В этих ситуациях вы все еще можете использовать маркеры ожидания, но Wait / Pulse имеет тенденцию быть проще. Самое замечательное в Wait / Pulse заключается в том, что Wait освобождает базовую блокировку во время ожидания. Например, в следующем примере мы читаем _x и _y в пределах безопасности блокировки - и все же эта блокировка снимается во время ожидания, чтобы другой поток мог обновить эти переменные:

lock (_locker)
{
  while (_x < 10 && _y < 20) Monitor.Wait (_locker);
}

Другой поток может затем обновлять _x и _y атомарно (посредством блокировки), а затем Pulse, чтобы подать сигнал официанту:

lock (_locker)
{
  _x = 20;
  _y = 30;
  Monitor.Pulse (_locker);
} 

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

С точки зрения эффективности / потребления ресурсов (на что, я думаю, вы ссылались), Wait / Pulse обычно быстрее и легче (так как имеет управляемую реализацию). На практике это редко имеет большое значение. И в этот момент Framework 4.0 включает управляемые версии ManualResetEvent и Semaphore с низким уровнем издержек (ManualResetEventSlim и SemaphoreSlim).

Framework 4.0 также предоставляет множество других опций синхронизации, которые уменьшают потребность в Wait / Pulse:

  • CountdownEvent
  • Barrier
  • PLINQ / параллелизм данных (AsParallel, Parallel.Invoke, Parallel.For, Parallel.ForEach)
  • Задачи и продолжения

Все они гораздо более высокого уровня, чем Wait / Pulse, и IMO предпочтительнее для написания надежного и поддерживаемого кода (при условии, что они решат поставленную задачу).

...