Введение

В информатике цикл событий — это конструкция программирования или шаблон проектирования, который ожидает и обрабатывает события или сообщения в программе. Цикл событий работает, отправляя запрос некоторому внутреннему или внешнему "источнику событий" (который обычно блокирует запрос до тех пор, пока не поступит событие), а затем вызывая соответствующий обработчик события ("обрабатывая событие"). Цикл событий также иногда называют диспетчером сообщений, циклом сообщений, механизмом обработки сообщений или циклом выполнения. Это также распространенный метод, используемый для реализации серверов, таких как веб-серверы. Цикл событий может использоваться совместно с реактором, если источник событий следует файсному интерфейсу, который можно выбрать или "проверить" (системный вызов Unix, а не фактический опрос). Цикл событий почти всегда работает асинхронно с инициатором сообщения. Когда цикл событий формирует центральную управляющую конструкцию программы, как это часто бывает, его можно назвать главным циклом или основным циклом событий. Это название уместно, поскольку такой цикл событий находится на самом высоком уровне управления в программе.

Передача сообщения

Насосы сообщений извлекают сообщения из очереди сообщений программы (выделенной и обычно принадлежащей базовой операционной системе) для последующей обработки. В строгом смысле, цикл обработки событий – это один из способов реализации межпроцессного взаимодействия. На самом деле, обработка сообщений присутствует во многих системах, включая компонент ядра операционной системы Mach. Цикл обработки событий – это конкретная техника реализации систем, использующих обмен сообщениями.

Управление сигналами

Одной из немногих вещей в Unix, не соответствующих файловому интерфейсу, являются асинхронные события (сигналы). Сигналы принимаются в обработчиках сигналов – небольших, ограниченных фрагментах кода, выполняемых во время приостановки основной задачи; если сигнал принимается и обрабатывается, пока задача блокируется в `select`, `select` вернется досрочно с ошибкой `EINTR`; если сигнал принимается, пока задача интенсивно использует процессор, задача будет приостановлена между инструкциями до возврата обработчика сигнала. Таким образом, очевидный способ обработки сигналов – установка глобального флага и проверка цикла событий на этот флаг непосредственно перед и после вызова `select`; если флаг установлен, сигнал обрабатывается аналогично событиям на файловых дескрипторах. К сожалению, это приводит к гонке данных: если сигнал приходит непосредственно между проверкой флага и вызовом `select`, он не будет обработан до тех пор, пока `select` не вернется по другой причине (например, из-за прерывания раздраженным пользователем). Решением, предложенным POSIX, является вызов `pselect`, который аналогичен `select`, но принимает дополнительный параметр `sigmask`, описывающий маску сигналов. Это позволяет приложению маскировать сигналы в основной задаче, а затем снимать маску на время вызова `select`, чтобы обработчики сигналов вызывались только тогда, когда приложение ожидает ввод-вывод. Однако реализации `pselect` не всегда были надежными; версии Linux до 2.6.16 не имеют системного вызова `pselect`, что вынуждает `glibc` эмулировать его методом, подверженным той же гонке данных, которую `pselect` призван избежать. Альтернативным, более переносимым решением является преобразование асинхронных событий в события, основанные на файлах, с использованием трюка с самописной трубой (self pipe trick), когда "обработчик сигнала записывает байт в трубу, другой конец которой отслеживается `select` в основной программе". В версии 2.6.22 ядра Linux был добавлен новый системный вызов `signalfd`, позволяющий принимать сигналы через специальный файловый дескриптор.

HTML/Javascript (англ.) русск.

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

Приложения для Windows

В операционной системе Microsoft Windows процесс, взаимодействующий с пользователем, должен принимать и реагировать на входящие сообщения, что почти всегда реализуется с помощью цикла обработки сообщений в этом процессе. В Windows сообщение приравнивается к событию, созданному и переданному операционной системе. Событием может быть взаимодействие с пользователем, сетевой трафик, системная обработка, активность таймера, межпроцессное взаимодействие и другие события. Для неинтерактивных событий, связанных только с операциями ввода-вывода, в Windows предусмотрены порты завершения ввода-вывода. Циклы обработки портов завершения ввода-вывода выполняются отдельно от цикла обработки сообщений и по умолчанию не взаимодействуют с ним. "Ядром" большинства приложений Win32 является функция WinMain, которая в цикле вызывает функцию GetMessage. Функция GetMessage блокируется до тех пор, пока не будет получено сообщение или "событие" (функция PeekMessage предоставляет неблокирующую альтернативу). После некоторой дополнительной обработки вызывается функция DispatchMessage, которая направляет сообщение соответствующему обработчику, также известному как WindowProc. Обычно сообщения, для которых не определен специальный WindowProc, направляются в DefWindowProc – обработчик по умолчанию. DispatchMessage вызывает WindowProc, связанный с дескриптором HWND сообщения (зарегистрированным с помощью функции RegisterClass).

Порядок сообщений

Более поздние версии Microsoft Windows гарантируют программисту доставку сообщений в цикл обработки сообщений приложения в том порядке, в котором они были зарегистрированы системой и её периферийными устройствами. Эта гарантия критически важна при проектировании многопоточных приложений. Однако для некоторых сообщений действуют иные правила, например, сообщения, которые всегда принимаются последними, или сообщения с установленным в документации иным приоритетом.

Круг событий Xlib

Приложения X, использующие Xlib напрямую, строятся вокруг семейства функций XNextEvent; XNextEvent блокируется до появления события в очереди событий, после чего приложение обрабатывает его соответствующим образом. Цикл обработки событий Xlib обрабатывает только события оконной системы; приложения, которым необходимо ожидать другие файлы и устройства, могут создать собственный цикл обработки событий из примитивов, таких как ConnectionNumber, но на практике обычно используют многопоточность. Очень мало программ используют Xlib напрямую. В более распространенном случае графические библиотеки (GUI toolkits), основанные на Xlib, обычно поддерживают добавление событий. Например, библиотеки на основе Xt Intrinsics имеют функции XtAppAddInput и XtAppAddTimeout. Следует отметить, что вызов функций Xlib из обработчика сигналов небезопасен, поскольку X-приложение может быть прервано в произвольном состоянии, например, внутри XNextEvent. Решение для X11R5, X11R6 и Xt можно найти в [здесь].

GLib цикл событий

Изначально цикл событий GLib был разработан для использования в GTK, но сейчас применяется и в приложениях, не имеющих графического интерфейса, таких как D-Bus. Отслеживаемые ресурсы – это набор файловых дескрипторов, представляющих интерес для приложения; процесс опроса будет прерван при поступлении сигнала или истечении времени ожидания (например, если приложение установило таймер или задачу в режиме простоя). Хотя GLib имеет встроенную поддержку событий файловых дескрипторов и завершения дочерних процессов, можно добавить источник событий для любого события, которое может быть обработано по модели "подготовка-проверка-отправка". Примерами библиотек приложений, построенных на цикле событий GLib, являются GStreamer и асинхронные методы ввода-вывода GnomeVFS, однако GTK остается наиболее заметной клиентской библиотекой. События из оконной системы (в X – данные, полученные из X-сокета) преобразуются GDK в события GTK и генерируются как сигналы GLib на объектах виджетов приложения.

Запуск циклов в программе MacOS Core Foundation

Разрешен только один CFRunLoop на поток, и к нему можно подключить произвольное количество источников и наблюдателей. Источники затем взаимодействуют с наблюдателями посредством цикла обработки событий, который организует постановку в очередь и отправку сообщений. В Cocoa CFRunLoop абстрагирован как NSRunLoop, позволяющий поместить в очередь для отправки любому объекту любое сообщение (эквивалентное вызову функции в средах выполнения, не поддерживающих рефлексию).