Я люблю опросы! Я? Да! Я? Да! Я? Да! Я все еще? Да! А сейчас? Да!
Как уже упоминали другие, это может быть невероятно неэффективно, если вы проводите опрос только для того, чтобы возвращать одно и то же неизменное состояние снова и снова. Таков рецепт сжигания циклов процессора и значительного сокращения времени автономной работы мобильных устройств. Конечно, это не расточительно, если вы каждый раз возвращаете новое и значимое состояние со скоростью, не превышающей желаемую.
Но главная причина, по которой я люблю опрос, заключается в его простоте и предсказуемости. Вы можете отследить код и легко увидеть, когда и где что-то произойдет, и в каком потоке. Теоретически, если бы мы жили в мире, где опрос был незначительной тратой (хотя реальность была далека от этого), то я считаю, что это упростит поддержание кода огромной сделкой. И в этом заключается преимущество опроса и вытягивания, так как я вижу, можем ли мы игнорировать производительность, даже если в этом случае мы не должны этого делать.
Когда я начал программировать в эпоху DOS, мои маленькие игры вращались вокруг опроса. Я скопировал некоторый ассемблерный код из книги, которую я едва понимал в отношении прерываний клавиатуры, и заставил ее хранить буфер состояний клавиатуры, и в этот момент мой основной цикл всегда опрашивал. Клавиша вверх вниз? Нет. Клавиша вверх вниз? Нет. Как насчет сейчас? Нет. В настоящее время? Да. Хорошо, переместите игрока.
И хотя это невероятно расточительно, я обнаружил, что гораздо легче рассуждать по сравнению с сегодняшними днями многозадачного и событийного программирования. Я точно знал, когда и где все будет происходить, и было проще поддерживать стабильную и предсказуемую частоту кадров без сбоев.
Таким образом, с тех пор я всегда пытался найти способ получить некоторые из преимуществ и предсказуемости этого, фактически не сжигая циклы ЦП, например, используя условные переменные, чтобы уведомить потоки о пробуждении, в какой момент они могут получить новое состояние, делай свое дело и возвращайся спать в ожидании, чтобы получить уведомление снова.
И каким-то образом я нахожу, что с очередями событий работать намного легче, по крайней мере, с шаблонами наблюдателей, даже несмотря на то, что они все еще не позволяют предсказать, куда вы в конечном итоге попадете или что в итоге произойдет. Они, по крайней мере, централизуют поток управления обработкой событий в нескольких ключевых областях системы и всегда обрабатывают эти события в одном и том же потоке, вместо того, чтобы отскочить от одной функции к где-то полностью удаленному и неожиданному за пределами центрального потока обработки событий. Таким образом, дихотомия не всегда должна быть между наблюдателями и опросами. Очереди событий - своего рода золотая середина там.
Но да, почему-то мне гораздо проще рассуждать о системах, которые делают вещи, которые аналогичным образом похожи на предсказуемые потоки управления, которые у меня были, когда я проводил опрос много лет назад, в то же время просто противодействуя тенденции к работе в времена, когда никаких изменений состояния не произошло. Так что есть преимущество, если вы можете делать это так, чтобы не сжигать циклы ЦП без необходимости, как с условными переменными.
Гомогенные петли
Хорошо, я получил отличный комментарий, Josh Caswell
который указал на некоторую глупость в моем ответе:
«как использование условных переменных для оповещения потоков о пробуждении» Похоже на организацию событий / наблюдателей, а не опрос
Технически сама переменная условия применяет шаблон наблюдателя для пробуждения / уведомления потоков, поэтому называть этот «опрос», вероятно, невероятно вводящим в заблуждение. Но я считаю, что это дает такое же преимущество, которое я нашел, как опрос в дни DOS (только с точки зрения потока управления и предсказуемости). Я постараюсь объяснить это лучше.
В те дни я находил привлекательным то, что вы можете посмотреть на фрагмент кода или проследить его и сказать: «Хорошо, весь этот раздел посвящен обработке событий клавиатуры. В этом разделе кода больше ничего не произойдет. И я точно знаю, что произойдет раньше, и я точно знаю, что произойдет после (например, физика и рендеринг). " Опрос состояний клавиатуры дал вам такую централизацию потока управления для обработки того, что должно происходить в ответ на это внешнее событие. Мы не сразу отреагировали на это внешнее событие. Мы ответили на это, когда нам будет удобно.
Когда мы используем систему на основе push, основанную на шаблоне Observer, мы часто теряем эти преимущества. Элемент управления может быть изменен, что вызывает событие изменения размера. Когда мы прослеживаем его, мы обнаруживаем, что мы находимся внутри экзотического элемента управления, который делает много пользовательских вещей при его изменении размера, что вызывает больше событий. В итоге мы полностью удивляемся, прослеживая во всех этих каскадных событиях то, где мы оказались в системе. Кроме того, мы могли бы обнаружить, что все это даже не происходит последовательно в каком-либо конкретном потоке, потому что поток A может изменить размер элемента управления здесь, в то время как поток B также изменит размер элемента управления позже. Поэтому мне всегда было очень трудно рассуждать о том, насколько сложно предсказать, где все происходит, а что произойдет.
Мне немного проще рассуждать об очереди событий, потому что она упрощает, где все это происходит, по крайней мере, на уровне потока. Однако может произойти много разнородных вещей. Очередь событий может содержать эклектичную смесь событий, которые нужно обработать, и каждое из них может удивить нас каскадом произошедших событий, порядком их обработки и тем, как мы в конечном итоге подпрыгиваем повсюду в кодовой базе. ,
То, что я считаю «ближайшим» к опросу, не будет использовать очередь событий, но откладывает очень однородный тип обработки. A PaintSystem
может быть предупрежден через переменную условия, что есть работа по рисованию, чтобы перерисовать определенные ячейки сетки окна, после чего он выполняет простой последовательный цикл по ячейкам и перерисовывает все внутри него в правильном z-порядке. Здесь может быть один уровень вызова косвенной / динамической диспетчеризации, чтобы вызвать события рисования в каждом виджете, находящемся в ячейке, которую нужно перекрасить, но это все - только один уровень косвенных вызовов. Переменная условия использует шаблон наблюдателя, чтобы предупредить, что в этот моментPaintSystem
что у него есть работа, но она не указывает ничего более этого, иPaintSystem
посвящена одной единой, очень однородной задаче на тот момент. Когда мы отлаживаем и отслеживаем PaintSystem's
код, мы знаем, что ничего не произойдет, кроме рисования.
Таким образом, это в основном сводит систему к тому, что эти вещи выполняют однородные циклы над данными, применяя очень особую ответственность за них, а не неоднородные циклы над разнородными типами данных, выполняющими многочисленные обязанности, которые мы могли бы получить при обработке очереди событий.
Мы стремимся к этому типу вещей:
when there's work to do:
for each thing:
apply a very specific and uniform operation to the thing
В отличие от:
when one specific event happens:
do something with relevant thing
in relevant thing's event:
do some more things
in thing1's triggered by thing's event:
do some more things
in thing2's event triggerd by thing's event:
do some more things:
in thing3's event triggered by thing2's event:
do some more things
in thing4's event triggered by thing1's event:
cause a side effect which shouldn't be happening
in this order or from this thread.
И так далее. И это не должно быть один поток на задачу. Один поток может применять логику макетов (изменение размера / перемещение) для элементов управления графическим интерфейсом и перерисовывать их, но он может не обрабатывать щелчки клавиатуры или мыши. Таким образом, вы можете рассматривать это как улучшение однородности очереди событий. Но нам не нужно использовать очередь событий и чередовать функции изменения размера и рисования. Мы можем сделать как:
in thread dedicated to layout and painting:
when there's work to do:
for each widget that needs resizing/reposition:
resize/reposition thing to target size/position
mark appropriate grid cells as needing repainting
for each grid cell that needs repainting:
repaint cell
go back to sleep
Таким образом, вышеприведенный подход просто использует условную переменную, чтобы уведомить поток, когда есть работа, но он не чередует различные типы событий (изменение размера в одном цикле, рисование в другом цикле, а не смесь обоих), и он не ' не пытайтесь сообщить, что именно нужно сделать, чтобы выполнить работу (поток «обнаруживает» это после пробуждения, просматривая общесистемные состояния ECS). Каждый цикл, который он выполняет, имеет очень однородный характер, что позволяет легко рассуждать о порядке, в котором все происходит.
Я не уверен, как назвать этот тип подхода. Я не видел, чтобы другие движки GUI делали это, и это своего рода экзотический подход к моему. Но раньше, когда я пытался реализовать многопоточные структуры GUI с использованием наблюдателей или очередей событий, у меня была огромная трудность при отладке, а также я столкнулся с некоторыми неясными условиями гонки и тупиками, которые я не был достаточно умен, чтобы исправить так, чтобы я чувствовал себя уверенно о решении (некоторые люди могли бы сделать это, но я не достаточно умен). Мой первый дизайн итерации просто вызывал слот непосредственно через сигнал, и некоторые слоты затем вызывали другие потоки для выполнения асинхронной работы, и об этом было сложнее всего рассуждать, и я споткнулся о условия гонки и взаимные блокировки. Вторая итерация использовала очередь событий, и об этом было немного проще думать, но для моего мозга не так-то просто сделать это, не столкнувшись с неясным тупиком и расой. Третья и последняя итерация использовала подход, описанный выше, и, наконец, это позволило мне создать многопоточную среду графического интерфейса, которую мог бы правильно реализовать даже такой тупой простак, как я.
Затем этот тип окончательного многопоточного дизайна графического интерфейса позволил мне придумать что-то еще, о чем было бы гораздо проще рассуждать, и избегать тех типов ошибок, которые я имел в виду, и одна из причин, по которой мне было гораздо проще рассуждать в меньше всего из-за этих однородных циклов и того, как они напоминают поток управления, похожий на тот, что я делал в дни DOS (хотя это не совсем опрос, а выполнение работы только тогда, когда есть работа, которую нужно выполнить). Идея заключалась в том, чтобы отойти как можно дальше от модели обработки событий, которая подразумевает неоднородные циклы, неоднородные побочные эффекты, неоднородные потоки управления, и все больше и больше работать в направлении однородных циклов, работающих равномерно на однородных данных и изолирующих и объединение побочных эффектов таким образом, чтобы легче было просто сосредоточиться на «что»