Является ли цикл обработки событий циклом for / while с оптимизированным опросом?

55

Я пытаюсь понять, что такое цикл событий. Часто объяснение заключается в том, что в цикле событий вы что-то делаете, пока не получите уведомление о том, что произошло событие. Затем вы обрабатываете событие и продолжаете делать то, что делали раньше.

Для сопоставления приведенного выше определения с примером. У меня есть сервер, который «слушает» в цикле событий, и при обнаружении соединения с сокетом данные с него считываются и отображаются, после чего сервер возобновляет / начинает прослушивание, как и раньше.


Тем не менее, это событие происходит, и мы получаем уведомление «просто так», и мне многое предстоит сделать. Вы можете сказать: «Это не« просто так », вы должны зарегистрировать прослушиватель событий». Но что такое прослушиватель событий, а функция, которая по какой-то причине не возвращается. Это в своем собственном цикле, ожидая уведомления, когда происходит событие? Должен ли прослушиватель событий также регистрировать прослушиватель событий? Где это заканчивается?


События - хорошая абстракция для работы, но только абстракция. Я считаю, что в конце концов, опрос неизбежен. Возможно, мы не делаем это в нашем коде, но более низкие уровни (реализация языка программирования или ОС) делают это для нас.

Это в основном сводится к следующему псевдокоду, который выполняется где-то достаточно низко, поэтому это не приводит к занятому ожиданию:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

Это мое понимание всей идеи, и я хотел бы услышать, если это правильно. Я открыт в том, чтобы признать, что вся идея в корне неверна, и в этом случае я хотел бы получить правильное объяснение.

TheMeaningfulEngineer
источник
3
Системы событий являются реализациями шаблона Observer . Понимание модели должно укрепить ваше понимание событий. Опрос не требуется.
Стивен Эверс
1
В конечном счете, да, даже если языковые конструкции абстрагируют его.
GrandmasterB
@SteveEvers В вашей вики-ссылке. Что EventSourceделать, если не опрашивать ввод с клавиатуры?
TheMeaningfulEngineer
@ Алан: Это может быть что угодно, но для ввода с клавиатуры, в частности, существуют API-интерфейсы для регистрации приложения в качестве прослушивания событий клавиатуры, которые затем можно использовать в качестве источника событий без участия опроса. Конечно, если вы заходите достаточно далеко, USB всегда опрашивает, но давайте предположим, что мы используем клавиатуру PS / 2, которая управляется прерываниями, а затем у вас есть стек ввода с клавиатуры, основанный на событиях с нулевым опросом.
Фоши
У меня были эти вопросы с годами, но я не могу сказать, почему я никогда не удосужился спросить его. Спасибо, теперь я удовлетворен пониманием, которым @Karl Bielefeldt просветил меня.
0xc0de

Ответы:

54

Большинство циклов событий приостанавливается, если нет готовых событий, а это означает, что операционная система не будет предоставлять заданию время выполнения, пока не произойдет событие.

Скажем, событие - это нажатие клавиши. Вы можете спросить, есть ли где-то в операционной системе цикл проверки нажатий клавиш. Ответ - нет. При нажатии клавиш генерируется прерывание , которое аппаратно обрабатывается асинхронно. Аналогично для таймеров, движений мыши, прибытия пакетов и т. Д.

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

Карл Билефельдт
источник
4
Разве прерывание не является изменением напряжения на проводе? Может ли это вызвать событие само по себе, или мы должны опросить контакт для определения значения напряжения?
TheMeaningfulEngineer
8
Это может вызвать событие само по себе. Процессор спроектирован таким образом. Фактически процессор может быть выведен из режима сна прерыванием.
Карл Билефельдт
2
Я смущен с заявлением Most event loops will block. Как это вписывается в «парадигму цикла событий, в отличие от использования потоков, использующих неблокирующие асинхронные вызовы»?
TheMeaningfulEngineer
1
Если вы посмотрите на циклы событий для библиотеки, такой как GTK +, они проверяют наличие новых событий, затем вызывают обработчики событий в цикле, но если нет никаких событий, они блокируют семафор, таймер или что-то еще. Отдельные разработчики создают свои собственные циклы событий, которые не блокируют пустую очередь событий, но блокируют широко используемые библиотеки. В противном случае циклы событий слишком неэффективны.
Карл Билефельдт
1
Я полагаю, вы могли бы взглянуть на это так, но это многопоточность в библиотеке и / или ОС, а не в коде приложения. Циклы событий абстрагируют проблемы синхронизации для разработчика приложения.
Карл Билефельдт
13

Я воспринимаю слушателя событий не как функцию, выполняющую свой собственный цикл, а как эстафету с первым бегуном, ожидающим стартовую пушку. Существенной причиной использования событий вместо опроса является то, что они более эффективны с циклами ЦП. Почему? Посмотрите на это с аппаратной части вверх (а не на исходный код вниз).

Рассмотрим веб-сервер. Когда ваш сервер звонит listen()и блокирует, ваш код занимает место ретранслятора. Когда поступает первый пакет нового соединения, сетевая карта начинает гонку, прерывая работу операционной системы. ОС запускает подпрограмму обработки прерываний (ISR), которая захватывает пакет. ISR передает эстафету подпрограмме более высокого уровня, которая устанавливает соединение. Как только соединение активируется, эта процедура передает эстафету listen(), которая передает эстафету вашему коду. На этом этапе вы можете делать то, что вы хотите с подключением. Насколько нам известно, между гонками каждый эстафета может идти в паб. Сила абстракции события в том, что ваш код не должен знать или заботиться.

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

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

Я думаю об этом как:

on(some event):    //I got the baton
     do stuff
     signal the next level up    //Pass the baton

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

CXW
источник
Это имеет смысл, однако, что делает цикл обработки событий, ожидая прерывания? Если это блокировка, разве это не многопоточный подход, который является парадигмой, противоположной циклу событий?
TheMeaningfulEngineer
Если в вашем коде есть петля, forever: { select(); do stuff; }то вы каждый раз возвращаетесь в гонку через петлю. Делаете ли вы это неоднократно из одного потока или параллельно в отдельных потоках или процессорах, я считаю каждое событие своей расой. Например, веб-браузер - это многопоточная программа с несколькими циклами событий в отдельных потоках, по крайней мере один для пользовательского интерфейса и один для каждой загружаемой страницы. Когда я пишу код, я задаю вопрос: «Как я могу достаточно быстро обрабатывать события?». Иногда ответом является цикл, иногда темы, часто комбинация.
cxw
Я занимался программированием на основе событий более пары десятилетий, и я нашел этот ответ очень запутанным. Чтобы слушатель работал, должен быть цикл, который ожидает событие, а затем направляет его всем зарегистрированным слушателям. (Предполагая, что мы говорим о программных, а не аппаратных прерываниях)
Брайан Оукли
8

Нет, это не «оптимизированный опрос». Цикл обработки событий использует управляемый прерываниями ввод-вывод вместо опроса.

Циклы «Пока», «Для» и т. Д. Являются циклами опроса.

«Опрос» - это процесс многократной проверки чего-либо. Поскольку код цикла выполняется непрерывно, и поскольку он представляет собой небольшой «плотный» цикл, у процессора мало времени для переключения задач и выполнения каких-либо других действий. Почти все «зависания», «зависания», «блокировки» или как вы хотите называть это, когда компьютер перестает отвечать на запросы, являются проявлением кода, застрявшего в незапланированном цикле опроса. Инструментарий покажет использование процессора на 100%.

Циклы событий, управляемые прерываниями, гораздо более эффективны, чем циклы опроса. Опрос - это чрезвычайно расточительное использование циклов ЦП, поэтому прилагаются все усилия для его устранения или минимизации.

Однако, чтобы оптимизировать качество кода, большинство языков стараются использовать парадигму цикла опроса как можно точнее для команд обработки событий, поскольку они служат функционально схожим целям в программе. Таким образом, поскольку опрос является более привычным способом ожидания нажатия клавиши или чего-то еще, для неопытного легко использовать его и запустить программу, которая может работать нормально сама по себе, но больше ничего не работает во время работы. Он «захватил» машину.

Как объяснялось в других ответах, при обработке событий, управляемых прерываниями, по сути, в ЦП устанавливается «флаг», и процесс «приостанавливается» (не разрешается запускаться) до тех пор, пока этот флаг не будет изменен каким-либо другим процессом (например, клавиатурой). драйвер меняет его, когда пользователь нажал клавишу). Если флаг является фактическим аппаратным состоянием, таким как линия, которую «поднимают высоко», это называется «прерыванием» или «аппаратным прерыванием». Однако большинство из них реализованы как просто адреса памяти в ЦП или в основной памяти (ОЗУ) и называются «семафорами».

Семафоры могут быть изменены под управлением программного обеспечения и, таким образом, могут обеспечить очень быстрый и простой механизм сигнализации между процессами программного обеспечения.

Прерывания, однако, могут быть изменены только аппаратно. Наиболее распространенное использование прерываний - это то, которое регулярно запускается внутренним тактовым чипом. Одним из бесчисленных видов программных действий, активируемых прерываниями часов, является смена семафоров.

Я много не учел, но должен был где-то остановиться. Пожалуйста, спросите, если вам нужно больше деталей.

DocSalvager
источник
7

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

stonemetal
источник
7

Я собираюсь пойти против всех других ответов, которые я вижу до сих пор, и сказать «да» . Я думаю, что другие ответы слишком усложняют вещи. С концептуальной точки зрения все циклы событий по существу:

while <the_program_is_running> {
    event=wait_for_next_event()
    process_event(event)
}

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

Брайан Оукли
источник
1
+1 за простоту. Но акцент делается на «ждать»; выполнение этого кода остановится в первой строке цикла и не продолжится, пока не произойдет событие. Это отличается от занятого цикла опроса, который многократно проверяет событие, пока оно не произойдет.
Том Паннинг
1

Не все триггеры событий обрабатываются в циклах. То, как я часто пишу свои собственные механизмы событий, было бы так:

interface Listener {
    void handle (EventInfo info);
}

List<Listener> registeredListeners

void triggerEvent (EventInfo info) {
    foreach (listener in registeredListeners) { // Memo 1
        listener.handle(info) // the handling may or may not be synchronous... your choice
    }
}

void somethingThatTriggersAnEvent () {
    blah
    blah
    blah
    triggerEvent(someGeneratedEventInfo)
    more blah
}

Обратите внимание, что хотя Memo 1 находится в цикле, цикл предназначен для уведомления каждого слушателя. Сам триггер события не обязательно находится в цикле.

Теоретически, ключевые события на уровне ОС могут использовать одну и ту же технику (хотя я думаю, что они вместо этого часто проводят опрос? Я просто размышляю здесь), при условии, что ОС предоставляет своего рода registerListenerAPI.

Томас Эдинг
источник