В информатике цикл событий — это программная конструкция или шаблон проектирования , который ожидает и отправляет события или сообщения в программе . Цикл событий работает, отправляя запрос какому-либо внутреннему или внешнему «поставщику событий» (который обычно блокирует запрос до тех пор, пока не произойдет событие), а затем вызывает соответствующий обработчик событий («отправляет событие»). Цикл событий также иногда называют диспетчером сообщений , циклом сообщений , насосом сообщений или циклом выполнения .
Это также распространенный метод, используемый для реализации таких серверов, как веб-серверы .
Цикл событий можно использовать в сочетании с реактором , если поставщик событий следует за файловым интерфейсом, который можно выбрать или «опросить» (системный вызов Unix, а не фактический опрос ). Цикл событий почти всегда работает асинхронно с отправителем сообщения.
Когда цикл событий образует центральную конструкцию потока управления программой, как это часто бывает, его можно назвать основным циклом или главным циклом событий . Это название уместно, поскольку такой цикл событий находится на самом высоком уровне управления внутри программы.
Говорят, что насосы сообщений «перекачивают» сообщения из очереди сообщений программы (назначенной и обычно принадлежащей базовой операционной системе) в программу для обработки. В самом строгом смысле цикл событий — это один из методов реализации межпроцессного взаимодействия . Фактически обработка сообщений существует во многих системах, включая компонент уровня ядра операционной системы Mach . Цикл событий — это особый метод реализации систем, использующих передачу сообщений .
Этот подход отличается от ряда других альтернатив:
Из-за преобладания графических пользовательских интерфейсов большинство современных приложений имеют основной цикл. Процедура get_next_message()
обычно предоставляется операционной системой и блокируется до тех пор, пока не станет доступно сообщение. Таким образом, вход в цикл осуществляется только тогда, когда есть что обрабатывать.
функция главная инициализировать() пока сообщение != выйти сообщение:= get_next_message() процесс_сообщение (сообщение) конец , пока функция завершения
В Unix парадигма « все есть файл » естественным образом приводит к циклу событий на основе файла. Чтение и запись в файлы, межпроцессное взаимодействие, сетевое взаимодействие и управление устройством выполняются с помощью файлового ввода-вывода, при этом цель идентифицируется файловым дескриптором . Системные вызовы select и poll позволяют отслеживать набор файловых дескрипторов на предмет изменения состояния, например, когда данные становятся доступными для чтения .
Например, рассмотрим программу, которая читает постоянно обновляемый файл и отображает его содержимое в системе X Window , которая взаимодействует с клиентами через сокет (либо домен Unix , либо Berkeley ):
def main (): file_fd = open ( "logfile.log" ) x_fd = open_display () struct_interface () while True : rlist , _ , _ = select . select ([ file_fd , x_fd ], [], []): если file_fd в rlist : data = file_fd . read () append_to_display ( data ) send_repaint_message () если x_fd в 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()
которого следует избегать.
Альтернативное, более переносимое решение — преобразовать асинхронные события в события на основе файлов с помощью трюка с самоконвейером [2] , где «обработчик сигнала записывает байт в канал, другой конец которого контролируется select()
в основной программе». [3] В ядре Linux версии 2.6.22 добавлен новый системный вызов signalfd()
, позволяющий получать сигналы через специальный файловый дескриптор.
Веб-страница и ее JavaScript обычно выполняются в однопоточном процессе веб-браузера . Процесс браузера обрабатывает сообщения из очереди по одному. С данным сообщением может быть связана функция JavaScript или другое событие браузера. Когда процесс браузера завершает обработку сообщения, он переходит к следующему сообщению в очереди.
В операционной системе Microsoft Windows процесс, который взаимодействует с пользователем, должен принимать входящие сообщения и реагировать на них, что почти неизбежно происходит с помощью цикла обработки сообщений в этом процессе. В Windows сообщение приравнивается к событию, созданному и наложенному на операционную систему. Событием может быть, среди прочего, взаимодействие пользователя, сетевой трафик, обработка системы, активность таймера, межпроцессное взаимодействие. Для неинтерактивных событий, предназначенных только для ввода-вывода, в Windows предусмотрены порты завершения ввода-вывода . Циклы портов завершения ввода-вывода выполняются отдельно от цикла сообщений и не взаимодействуют с циклом сообщений «из коробки».
«Сердцем» большинства приложений Win32 является функция WinMain(), которая в цикле вызывает GetMessage(). GetMessage() блокируется до тех пор, пока не будет получено сообщение или «событие» (с функцией PeekMessage() в качестве неблокирующей альтернативы). После некоторой дополнительной обработки он вызывает DispatchMessage(), который отправляет сообщение соответствующему обработчику, также известному как WindowProc . Обычно сообщения, у которых нет специального WindowProc(), отправляются в DefWindowProc , который используется по умолчанию. DispatchMessage() вызывает WindowProc дескриптора HWND сообщения (зарегистрированного с помощью функции RegisterClass()).
Более поздние версии Microsoft Windows гарантируют программисту, что сообщения будут доставлены в цикл сообщений приложения в том порядке, в котором они были восприняты системой и ее периферийными устройствами. Эта гарантия важна при рассмотрении последствий проектирования многопоточных приложений.
Однако для некоторых сообщений действуют другие правила, например сообщения, которые всегда принимаются последними, или сообщения с другим документированным приоритетом. [4]
Приложения X , использующие Xlib напрямую, построены на основе XNextEvent
семейства функций; XNextEvent
блокируется до тех пор, пока событие не появится в очереди событий, после чего приложение обрабатывает его соответствующим образом. Цикл событий Xlib обрабатывает только события оконной системы; приложения, которым необходима возможность ожидания других файлов и устройств, могут создавать свои собственные циклы событий из таких примитивов, как ConnectionNumber
, но на практике они склонны использовать многопоточность .
Очень немногие программы используют Xlib напрямую. В более распространенном случае наборы инструментов GUI, основанные на Xlib, обычно поддерживают добавление событий. Например, наборы инструментов, основанные на Xt Intrinsics, имеют XtAppAddInput()
и XtAppAddTimeout()
.
Обратите внимание, что вызывать функции Xlib из обработчика сигналов небезопасно, поскольку приложение X могло быть прервано в произвольном состоянии, например, внутри XNextEvent
. См. [1] решение для X11R5, X11R6 и Xt.
Цикл событий GLib изначально был создан для использования в GTK , но теперь используется и в приложениях без графического интерфейса, таких как D-Bus . Опрашиваемый ресурс представляет собой набор дескрипторов файлов , которые интересуют приложение; блок опроса будет прерван, если поступит сигнал или истечет таймаут (например, если приложение указало таймаут или задачу ожидания). Хотя GLib имеет встроенную поддержку файловых дескрипторов и дочерних событий завершения, можно добавить источник событий для любого события, которое может быть обработано в модели подготовки-проверки-отправки.[2]
Библиотеки приложений, построенные на цикле событий GLib, включают GStreamer и методы асинхронного ввода-вывода GnomeVFS , но GTK остается наиболее заметной клиентской библиотекой. События из оконной системы (в X — считывание из сокета X ) преобразуются GDK в события GTK и отправляются в виде сигналов GLib на объекты виджетов приложения.
Для каждого потока разрешен ровно один CFRunLoop, и может быть присоединено произвольное количество источников и наблюдателей. Затем источники общаются с наблюдателями через цикл выполнения, который организует очередь и отправку сообщений.
CFRunLoop абстрагируется в Cocoa как NSRunLoop, который позволяет любому сообщению (эквивалентному вызову функции в неотражающих средах выполнения) ставиться в очередь для отправки любому объекту.