Цикл событий
В информатике цикл событий (также известный как диспетчер сообщений , цикл сообщений , насос сообщений или цикл выполнения ) — это программная конструкция или шаблон проектирования , который ожидает и отправляет события или сообщения в программе . Цикл событий работает путем отправки запроса к какому-либо внутреннему или внешнему «поставщику событий» (который обычно блокирует запрос до тех пор, пока не произойдет событие), а затем вызывает соответствующий обработчик событий («отправляет событие»).
Он также обычно реализуется на таких серверах, как веб-серверы .
Цикл событий может использоваться в сочетании с реактором , если поставщик событий следует за файловым интерфейсом , который можно выбрать или «опросить» (системный вызов Unix, а не фактический опрос ). Цикл событий почти всегда работает асинхронно с отправителем сообщения.
Когда цикл событий образует центральную конструкцию потока управления программой, как это часто бывает, его можно назвать основным циклом или главным циклом событий . Это название уместно, поскольку такой цикл событий находится на самом высоком уровне управления внутри программы.
Передача сообщений
[ редактировать ]программы Говорят, что насосы сообщений «перекачивают» сообщения из очереди сообщений (назначенной и обычно принадлежащей базовой операционной системе) в программу для обработки. В самом строгом смысле цикл событий — это один из методов реализации межпроцессного взаимодействия . Фактически обработка сообщений существует во многих системах, включая уровня ядра компонент операционной системы Mach . Цикл событий — это особый метод реализации систем, использующих передачу сообщений .
Альтернативные конструкции
[ редактировать ]Этот подход отличается от ряда других альтернатив:
- Традиционно программа просто запускалась один раз, а затем закрывалась. Этот тип программ был очень распространен на заре компьютерной эры и не имел какой-либо формы взаимодействия с пользователем. Это до сих пор часто используется, особенно в виде программ, управляемых из командной строки . Любые параметры настраиваются заранее и передаются за один раз при запуске программы.
- Дизайн на основе меню. Они по-прежнему могут содержать основной цикл, но обычно не считаются управляемыми событиями в обычном смысле. [ нужна ссылка ] Вместо этого пользователю предоставляется постоянно сужающийся набор опций до тех пор, пока задача, которую он хочет выполнить, не станет единственным доступным вариантом. Доступна ограниченная интерактивность через меню.
Использование
[ редактировать ]Из-за преобладания графических пользовательских интерфейсов большинство современных приложений имеют основной цикл. get_next_message()
процедура обычно предоставляется операционной системой и блокируется до тех пор, пока сообщение не станет доступным. Таким образом, вход в цикл осуществляется только тогда, когда есть что обрабатывать.
function main initialize() while message != quit message := get_next_message() process_message(message) end while end function
Файловый интерфейс
[ редактировать ]В Unix парадигма « все есть файл » естественным образом приводит к циклу событий на основе файла. Чтение и запись в файлы, межпроцессное взаимодействие, сетевое взаимодействие и управление устройством выполняются с помощью файлового ввода-вывода, при этом цель идентифицируется файловым дескриптором . Системные вызовы select и poll позволяют отслеживать набор файловых дескрипторов на предмет изменения состояния, например, когда данные становятся доступными для чтения.
Например, рассмотрим программу, которая читает постоянно обновляемый файл и отображает его содержимое в системе X Window , которая взаимодействует с клиентами через сокет (либо домен Unix , либо Berkeley ):
def main():
file_fd = open("logfile.log")
x_fd = open_display()
construct_interface()
while True:
rlist, _, _ = select.select([file_fd, x_fd], [], []):
if file_fd in rlist:
data = file_fd.read()
append_to_display(data)
send_repaint_message()
if x_fd in rlist:
process_x_messages()
Обработка сигналов
[ редактировать ]Одна из немногих вещей в Unix, которая не соответствует файловому интерфейсу, — это асинхронные события ( сигналы ). Сигналы принимаются в обработчиках сигналов — небольших ограниченных фрагментах кода, которые выполняются, пока остальная часть задачи приостановлена; если сигнал получен и обработан во время блокировки задачи select()
, select вернется раньше с EINTR ; Если сигнал получен, когда задача привязана к ЦП , задача будет приостановлена между инструкциями до тех пор, пока обработчик сигнала не вернется.
Таким образом, очевидный способ обработки сигналов состоит в том, чтобы обработчики сигналов установили глобальный флаг и проверили его в цикле событий непосредственно до и после select()
вызов; если он установлен, обрабатывайте сигнал так же, как и с событиями в файловых дескрипторах. К сожалению, это приводит к состоянию гонки : если сигнал поступает сразу между проверкой флага и вызовом select()
, он не будет обработан до тех пор, пока select()
возвращается по какой-либо другой причине (например, если его прервал расстроенный пользователь).
Решение, полученное с помощью POSIX, - это pselect()
вызов, который похож на select()
но требует доп. sigmask
параметр, описывающий маску сигнала . Это позволяет приложению маскировать сигналы в основной задаче, а затем снимать маску на время выполнения задачи. select()
вызов таким образом, чтобы обработчики сигналов вызывались только тогда, когда приложение привязано к вводу-выводу . Однако реализации pselect()
не всегда были надежными; версии Linux до 2.6.16 не имеют pselect()
системный вызов, [1] заставить glibc эмулировать его с помощью метода, склонного к тому же состоянию гонки pselect()
призвано избежать.
Альтернативное, более портативное решение — преобразовать асинхронные события в события на основе файлов с помощью трюка self-pipe , [2] где «обработчик сигнала записывает байт в канал, другой конец которого контролируется select()
в основной программе». [3] В ядре Linux версии 2.6.22 появился новый системный вызов signalfd()
добавлена возможность приема сигналов через специальный файловый дескриптор.
Реализации
[ редактировать ]HTML/Яваскрипт
[ редактировать ]Веб-страница и ее JavaScript обычно выполняются в однопоточном процессе веб-браузера . Процесс браузера обрабатывает сообщения из очереди по одному. JavaScript функция С данным сообщением может быть связана или другое событие браузера. Когда процесс браузера завершает обработку сообщения, он переходит к следующему сообщению в очереди.
Приложения для Windows
[ редактировать ]В операционной системе Microsoft Windows процесс, который взаимодействует с пользователем, должен принимать входящие сообщения и реагировать на них, что почти неизбежно происходит с помощью цикла обработки сообщений в этом процессе. В Windows сообщение приравнивается к событию, созданному и наложенному на операционную систему. Событием может быть, среди прочего, взаимодействие пользователя, сетевой трафик, обработка системы, активность таймера, межпроцессное взаимодействие. Для неинтерактивных событий, предназначенных только для ввода-вывода, в Windows предусмотрены порты завершения ввода-вывода . Циклы портов завершения ввода-вывода выполняются отдельно от цикла сообщений и не взаимодействуют с циклом сообщений «из коробки».
«Сердцем» большинства Win32 приложений является функция WinMain() вызывает GetMessage() , которая в цикле . GetMessage() блокируется до тех пор, пока не будет получено сообщение или «событие» (с функцией PeekMessage() в качестве неблокирующей альтернативы). После некоторой дополнительной обработки он вызывает DispatchMessage() , который отправляет сообщение соответствующему обработчику, также известному как WindowProc . Обычно сообщения, у которых нет специального WindowProc(), отправляются в DefWindowProc , который используется по умолчанию. DispatchMessage() вызывает WindowProc дескриптора HWND ( сообщения зарегистрированного с помощью функции RegisterClass() ).
Порядок сообщений
[ редактировать ]Более поздние версии Microsoft Windows гарантируют программисту, что сообщения будут доставлены в цикл сообщений приложения в том порядке, в котором они были восприняты системой и ее периферийными устройствами. Эта гарантия важна при рассмотрении последствий проектирования многопоточных приложений.
Однако для некоторых сообщений действуют другие правила, например сообщения, которые всегда принимаются последними, или сообщения с другим документированным приоритетом. [4]
X оконная система
[ редактировать ]Цикл событий Xlib
[ редактировать ]Приложения X , использующие Xlib напрямую, построены на основе XNextEvent
семейство функций; XNextEvent
блокируется до тех пор, пока событие не появится в очереди событий, после чего приложение обрабатывает его соответствующим образом. Цикл событий Xlib обрабатывает только события оконной системы; приложения, которым необходимо иметь возможность ожидать других файлов и устройств, могут создавать собственный цикл событий из таких примитивов, как ConnectionNumber
, но на практике склонны использовать многопоточность .
Очень немногие программы используют Xlib напрямую. В более распространенном случае наборы инструментов GUI, основанные на Xlib, обычно поддерживают добавление событий. Например, наборы инструментов, основанные на Xt Intrinsics, имеют XtAppAddInput()
и XtAppAddTimeout()
.
Обратите внимание, что вызывать функции Xlib из обработчика сигналов небезопасно, поскольку приложение X могло быть прервано в произвольном состоянии, например, в XNextEvent
. См. [1] решение для X11R5, X11R6 и Xt.
Цикл событий GLib
[ редактировать ]Цикл событий GLib изначально был создан для использования в GTK , но теперь используется и в приложениях без графического интерфейса, таких как D-Bus . Опрашиваемый ресурс представляет собой набор дескрипторов файлов, которые интересуют приложение; блок опроса будет прерван, если поступит сигнал или истечет таймаут (например, если приложение указало таймаут или задачу ожидания). Хотя GLib имеет встроенную поддержку файловых дескрипторов и дочерних событий завершения, можно добавить источник событий для любого события, которое может быть обработано в модели подготовки-проверки-отправки. [2]
Библиотеки приложений, построенные на цикле событий GLib, включают GStreamer и асинхронного ввода-вывода методы GnomeVFS , но GTK остается наиболее заметной клиентской библиотекой. События из оконной системы (в X X — считывание из сокета ) преобразуются GDK в события GTK и отправляются в виде сигналов GLib на объекты виджетов приложения.
Циклы выполнения macOS Core Foundation
[ редактировать ]Для каждого потока разрешен ровно один CFRunLoop, и может быть присоединено произвольное количество источников и наблюдателей. Затем источники общаются с наблюдателями через цикл выполнения, который организует очередь и отправку сообщений.
CFRunLoop абстрагируется в Cocoa как NSRunLoop, который позволяет любому сообщению (эквивалентному вызову функции в неотражающих средах выполнения) ставиться в очередь для отправки любому объекту.
См. также
[ редактировать ]- Асинхронный ввод-вывод
- Программирование, управляемое событиями
- Межпроцессное взаимодействие
- Передача сообщений
- Игровой цикл в программировании игр
Ссылки
[ редактировать ]- ^ «Linux_2_6_16 — новички в ядре Linux» . kernelnewbies.org . Проверено 03 марта 2021 г.
- ^ Диджей Бернштейн. «Трюк с самодельной трубкой» .
- ^ ОШИБКИ, Linux программиста Руководство – Системные вызовы : синхронное мультиплексирование ввода-вывода –
- ^ Функция GetMessage() со списком приоритетов сообщений.