Существуют ли сценарии, в которых опрос событий был бы лучше, чем использование шаблона наблюдателя ? У меня есть страх перед использованием опроса, и я бы начал его использовать, только если бы кто-то дал мне хороший сценарий. Все, о чем я могу думать, - это то, как шаблон наблюдателя лучше, чем опрос. Рассмотрим этот сценарий:
Вы программируете симулятор автомобиля. Машина это объект. Как только машина включится, вы захотите воспроизвести аудиоклип "vroom vroom".
Вы можете смоделировать это двумя способами:
Опрос : Опрашивайте объект автомобиля каждую секунду, чтобы увидеть, включен ли он. Когда он включен, воспроизведите звуковой клип.
Образец наблюдателя : Сделайте автомобиль Субъектом образца наблюдателя. Пусть оно опубликует событие «on» всем наблюдателям, когда оно само включится. Создайте новый звуковой объект, который слушает машину. Пусть он реализует обратный вызов «on», который воспроизводит аудиоклип.
В этом случае, я думаю, шаблон наблюдателя выигрывает. Во-первых, опрос является более интенсивным процессором. Во-вторых, аудиоклип не срабатывает сразу при включении автомобиля. Из-за периода опроса может быть разрыв до 1 секунды.
источник
Ответы:
Представьте, что вы хотите получать уведомления о каждом цикле работы двигателя, например, отображать измерение оборотов для водителя.
Шаблон наблюдателя: Механизм публикует событие «цикл двигателя» всем наблюдателям для каждого цикла. Создайте прослушиватель, который считает события и обновляет отображение RPM.
Опрос: Дисплей оборотов в минуту запрашивает у двигателя счетчик циклов двигателя и соответственно обновляет дисплей оборотов.
В этом случае шаблон наблюдателя, вероятно, потерял бы: цикл двигателя является высокочастотным, высокоприоритетным процессом, вы не хотите откладывать или останавливать этот процесс только для обновления отображения. Вы также не хотите разбивать пул потоков событиями цикла двигателя.
PS: Я также часто использую шаблон опроса в распределенном программировании:
Шаблон наблюдателя: Процесс A отправляет сообщение процессу B, в котором говорится, что «каждый раз, когда происходит событие E, отправляйте сообщение процессу A».
Шаблон опроса: процесс A регулярно отправляет сообщение процессу B, в котором говорится: «Если событие E произошло с момента последнего опроса, отправьте мне сообщение сейчас».
Шаблон опроса производит немного большую нагрузку на сеть. Но у модели наблюдателя есть и недостатки:
источник
Опрос лучше, если процесс опроса проходит значительно медленнее, чем опрашиваемые объекты. Если вы записываете события в базу данных, часто лучше опросить всех производителей событий, собрать все события, произошедшие с момента последнего опроса, а затем записать их в одну транзакцию. Если вы попытаетесь записать каждое событие в том виде, как оно произошло, вы не сможете поддерживать его, и в конечном итоге у вас возникнут проблемы, когда ваши входные очереди заполнятся. Это также имеет больше смысла в слабосвязанных распределенных системах, где задержка высока или установка и разрыв соединения стоят дорого. Я считаю, что системы опросов легче писать и понимать, но в большинстве ситуаций наблюдатели или потребители, управляемые событиями, похоже, предлагают более высокую производительность (по моему опыту).
источник
Опрос намного проще заставить работать по сети, когда соединения могут перестать работать, серверы могут работать и т. Д. Помните, что в конце дня сокет TCP нуждается в «опросе» сообщений keep-a-live, иначе сервер примет клиента ушел
Опрос также хорош, если вы хотите, чтобы пользовательский интерфейс обновлялся, но базовые объекты меняются очень быстро , нет смысла обновлять пользовательский интерфейс чаще, чем несколько раз в секунду в большинстве приложений.
При условии, что сервер может ответить «без изменений» по очень низкой цене, и вы не будете опрашивать слишком часто, и у вас не будет тысяч опросов клиентов, тогда опрос будет очень хорошо работать в реальной жизни.
Однако для случаев « в памяти » я по умолчанию использую шаблон наблюдателя, поскольку он обычно является наименьшей работой.
источник
У опроса есть некоторые недостатки, вы в основном указали их уже в своем вопросе.
Однако это может быть лучшим решением, когда вы хотите действительно отделить наблюдаемое от любых наблюдателей. Но иногда может быть лучше использовать наблюдаемую оболочку для объекта, который будет наблюдаться в таких случаях.
Я бы использовал опрос только тогда, когда наблюдаемое не может наблюдаться при взаимодействии объектов, что часто бывает, например, при запросах к базам данных, когда у вас не может быть обратных вызовов. Другой проблемой может быть многопоточность, где зачастую безопаснее опрашивать и обрабатывать сообщения, а не вызывать объекты напрямую, чтобы избежать проблем параллелизма.
источник
Для хорошего примера того, как опрос вступает во владение от уведомления, посмотрите на сетевые стеки операционной системы.
Для Linux было большой проблемой, когда сетевой стек включил NAPI, сетевой API, который позволял драйверам переключаться из режима прерывания (уведомления) в режим опроса.
При наличии нескольких гигабитных интерфейсов Ethernet прерывания часто перегружают ЦП, в результате чего система работает медленнее, чем следовало бы. При опросе сетевые карты собирают пакеты в буферах до тех пор, пока они не будут опрошены, или карты даже записывают пакеты в память через DMA. Затем, когда операционная система готова, она опрашивает карту на предмет всех своих данных и выполняет стандартную обработку TCP / IP.
Режим опроса позволяет ЦПУ собирать данные Ethernet с максимальной скоростью обработки без бесполезной нагрузки прерывания. Режим прерывания позволяет процессору бездействовать между пакетами, когда работа не так занята.
Секрет в том, когда переключаться с одного режима на другой. Каждый режим имеет свои преимущества и должен использоваться в нужном месте.
источник
Я люблю опросы! Я? Да! Я? Да! Я? Да! Я все еще? Да! А сейчас? Да!
Как уже упоминали другие, это может быть невероятно неэффективно, если вы проводите опрос только для того, чтобы возвращать одно и то же неизменное состояние снова и снова. Таков рецепт сжигания циклов процессора и значительного сокращения времени автономной работы мобильных устройств. Конечно, это не расточительно, если вы каждый раз возвращаете новое и значимое состояние со скоростью, не превышающей желаемую.
Но главная причина, по которой я люблю опрос, заключается в его простоте и предсказуемости. Вы можете отследить код и легко увидеть, когда и где что-то произойдет, и в каком потоке. Теоретически, если бы мы жили в мире, где опрос был незначительной тратой (хотя реальность была далека от этого), то я считаю, что это упростит поддержание кода огромной сделкой. И в этом заключается преимущество опроса и вытягивания, так как я вижу, можем ли мы игнорировать производительность, даже если в этом случае мы не должны этого делать.
Когда я начал программировать в эпоху DOS, мои маленькие игры вращались вокруг опроса. Я скопировал некоторый ассемблерный код из книги, которую я едва понимал в отношении прерываний клавиатуры, и заставил ее хранить буфер состояний клавиатуры, и в этот момент мой основной цикл всегда опрашивал. Клавиша вверх вниз? Нет. Клавиша вверх вниз? Нет. Как насчет сейчас? Нет. В настоящее время? Да. Хорошо, переместите игрока.
И хотя это невероятно расточительно, я обнаружил, что гораздо легче рассуждать по сравнению с сегодняшними днями многозадачного и событийного программирования. Я точно знал, когда и где все будет происходить, и было проще поддерживать стабильную и предсказуемую частоту кадров без сбоев.
Таким образом, с тех пор я всегда пытался найти способ получить некоторые из преимуществ и предсказуемости этого, фактически не сжигая циклы ЦП, например, используя условные переменные, чтобы уведомить потоки о пробуждении, в какой момент они могут получить новое состояние, делай свое дело и возвращайся спать в ожидании, чтобы получить уведомление снова.
И каким-то образом я нахожу, что с очередями событий работать намного легче, по крайней мере, с шаблонами наблюдателей, даже несмотря на то, что они все еще не позволяют предсказать, куда вы в конечном итоге попадете или что в итоге произойдет. Они, по крайней мере, централизуют поток управления обработкой событий в нескольких ключевых областях системы и всегда обрабатывают эти события в одном и том же потоке, вместо того, чтобы отскочить от одной функции к где-то полностью удаленному и неожиданному за пределами центрального потока обработки событий. Таким образом, дихотомия не всегда должна быть между наблюдателями и опросами. Очереди событий - своего рода золотая середина там.
Но да, почему-то мне гораздо проще рассуждать о системах, которые делают вещи, которые аналогичным образом похожи на предсказуемые потоки управления, которые у меня были, когда я проводил опрос много лет назад, в то же время просто противодействуя тенденции к работе в времена, когда никаких изменений состояния не произошло. Так что есть преимущество, если вы можете делать это так, чтобы не сжигать циклы ЦП без необходимости, как с условными переменными.
Гомогенные петли
Хорошо, я получил отличный комментарий,
Josh Caswell
который указал на некоторую глупость в моем ответе:Технически сама переменная условия применяет шаблон наблюдателя для пробуждения / уведомления потоков, поэтому называть этот «опрос», вероятно, невероятно вводящим в заблуждение. Но я считаю, что это дает такое же преимущество, которое я нашел, как опрос в дни DOS (только с точки зрения потока управления и предсказуемости). Я постараюсь объяснить это лучше.
В те дни я находил привлекательным то, что вы можете посмотреть на фрагмент кода или проследить его и сказать: «Хорошо, весь этот раздел посвящен обработке событий клавиатуры. В этом разделе кода больше ничего не произойдет. И я точно знаю, что произойдет раньше, и я точно знаю, что произойдет после (например, физика и рендеринг). " Опрос состояний клавиатуры дал вам такую централизацию потока управления для обработки того, что должно происходить в ответ на это внешнее событие. Мы не сразу отреагировали на это внешнее событие. Мы ответили на это, когда нам будет удобно.
Когда мы используем систему на основе push, основанную на шаблоне Observer, мы часто теряем эти преимущества. Элемент управления может быть изменен, что вызывает событие изменения размера. Когда мы прослеживаем его, мы обнаруживаем, что мы находимся внутри экзотического элемента управления, который делает много пользовательских вещей при его изменении размера, что вызывает больше событий. В итоге мы полностью удивляемся, прослеживая во всех этих каскадных событиях то, где мы оказались в системе. Кроме того, мы могли бы обнаружить, что все это даже не происходит последовательно в каком-либо конкретном потоке, потому что поток A может изменить размер элемента управления здесь, в то время как поток B также изменит размер элемента управления позже. Поэтому мне всегда было очень трудно рассуждать о том, насколько сложно предсказать, где все происходит, а что произойдет.
Мне немного проще рассуждать об очереди событий, потому что она упрощает, где все это происходит, по крайней мере, на уровне потока. Однако может произойти много разнородных вещей. Очередь событий может содержать эклектичную смесь событий, которые нужно обработать, и каждое из них может удивить нас каскадом произошедших событий, порядком их обработки и тем, как мы в конечном итоге подпрыгиваем повсюду в кодовой базе. ,
То, что я считаю «ближайшим» к опросу, не будет использовать очередь событий, но откладывает очень однородный тип обработки. A
PaintSystem
может быть предупрежден через переменную условия, что есть работа по рисованию, чтобы перерисовать определенные ячейки сетки окна, после чего он выполняет простой последовательный цикл по ячейкам и перерисовывает все внутри него в правильном z-порядке. Здесь может быть один уровень вызова косвенной / динамической диспетчеризации, чтобы вызвать события рисования в каждом виджете, находящемся в ячейке, которую нужно перекрасить, но это все - только один уровень косвенных вызовов. Переменная условия использует шаблон наблюдателя, чтобы предупредить, что в этот моментPaintSystem
что у него есть работа, но она не указывает ничего более этого, иPaintSystem
посвящена одной единой, очень однородной задаче на тот момент. Когда мы отлаживаем и отслеживаемPaintSystem's
код, мы знаем, что ничего не произойдет, кроме рисования.Таким образом, это в основном сводит систему к тому, что эти вещи выполняют однородные циклы над данными, применяя очень особую ответственность за них, а не неоднородные циклы над разнородными типами данных, выполняющими многочисленные обязанности, которые мы могли бы получить при обработке очереди событий.
Мы стремимся к этому типу вещей:
В отличие от:
И так далее. И это не должно быть один поток на задачу. Один поток может применять логику макетов (изменение размера / перемещение) для элементов управления графическим интерфейсом и перерисовывать их, но он может не обрабатывать щелчки клавиатуры или мыши. Таким образом, вы можете рассматривать это как улучшение однородности очереди событий. Но нам не нужно использовать очередь событий и чередовать функции изменения размера и рисования. Мы можем сделать как:
Таким образом, вышеприведенный подход просто использует условную переменную, чтобы уведомить поток, когда есть работа, но он не чередует различные типы событий (изменение размера в одном цикле, рисование в другом цикле, а не смесь обоих), и он не ' не пытайтесь сообщить, что именно нужно сделать, чтобы выполнить работу (поток «обнаруживает» это после пробуждения, просматривая общесистемные состояния ECS). Каждый цикл, который он выполняет, имеет очень однородный характер, что позволяет легко рассуждать о порядке, в котором все происходит.
Я не уверен, как назвать этот тип подхода. Я не видел, чтобы другие движки GUI делали это, и это своего рода экзотический подход к моему. Но раньше, когда я пытался реализовать многопоточные структуры GUI с использованием наблюдателей или очередей событий, у меня была огромная трудность при отладке, а также я столкнулся с некоторыми неясными условиями гонки и тупиками, которые я не был достаточно умен, чтобы исправить так, чтобы я чувствовал себя уверенно о решении (некоторые люди могли бы сделать это, но я не достаточно умен). Мой первый дизайн итерации просто вызывал слот непосредственно через сигнал, и некоторые слоты затем вызывали другие потоки для выполнения асинхронной работы, и об этом было сложнее всего рассуждать, и я споткнулся о условия гонки и взаимные блокировки. Вторая итерация использовала очередь событий, и об этом было немного проще думать, но для моего мозга не так-то просто сделать это, не столкнувшись с неясным тупиком и расой. Третья и последняя итерация использовала подход, описанный выше, и, наконец, это позволило мне создать многопоточную среду графического интерфейса, которую мог бы правильно реализовать даже такой тупой простак, как я.
Затем этот тип окончательного многопоточного дизайна графического интерфейса позволил мне придумать что-то еще, о чем было бы гораздо проще рассуждать, и избегать тех типов ошибок, которые я имел в виду, и одна из причин, по которой мне было гораздо проще рассуждать в меньше всего из-за этих однородных циклов и того, как они напоминают поток управления, похожий на тот, что я делал в дни DOS (хотя это не совсем опрос, а выполнение работы только тогда, когда есть работа, которую нужно выполнить). Идея заключалась в том, чтобы отойти как можно дальше от модели обработки событий, которая подразумевает неоднородные циклы, неоднородные побочные эффекты, неоднородные потоки управления, и все больше и больше работать в направлении однородных циклов, работающих равномерно на однородных данных и изолирующих и объединение побочных эффектов таким образом, чтобы легче было просто сосредоточиться на «что»
источник
LayoutSystem
который обычно спит, но когда пользователь изменяет размер элемента управления, он будет использовать переменную условия для пробужденияLayoutSystem
. ЗатемLayoutSystem
изменяет размеры всех необходимых элементов управления и возвращается в режим сна. При этом прямоугольные области, в которых находятся виджеты, помечаются как нуждающиеся в обновлении, после чегоPaintSystem
пробуждение и прохождение через эти прямоугольные области перерисовываются те, которые необходимо перерисовать в плоском последовательном цикле.Я даю вам обзор более концептуального мышления о скороговорке наблюдателя. Подумайте о сценарии, как подписка на каналы YouTube. Есть количество пользователей, которые подписаны на канал, и как только на канале будет какое-либо обновление, которое состоит из множества видео, подписчик получает уведомление об изменении в этом конкретном канале. Таким образом, мы пришли к выводу, что если канал является СУБЪЕКТОМ, у которого есть возможность подписаться, отмените подписку и уведомите всех НАБЛЮДАТЕЛЕЙ, которые зарегистрированы в канале.
источник