Цикл событий

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

В информатике, цикл событий [1] [2] [3] , диспетчер сообщений, цикл сообщений, помпа сообщений, или рабочий цикл — программная конструкция, которая ожидает прибытия и производит рассылку событий или сообщений в программе. Он работает, делая запрос к некоторому внутреннему или внешнему «поставщику событий» (который, как правило блокирует запрос до тех пор, пока событие не появится), а затем вызывает соответствующий обработчик события («отправляет событие»). Цикл событий может быть использован в сочетании с паттерном проектирования Reactor, если поставщик событий соответствует файловому интерфейсу, который может быть выбран (имеется в виду методом select) или «опрашивается» (имеется в виду системный вызов Unix, а не фактический опрос). Цикл событий почти всегда работает асинхронно с отправителем.

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

Передача сообщений[править | править вики-текст]

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

Использование[править | править вики-текст]

Традиционно программы писались в синхронном стиле: всё взаимодействие с программой сводилось либо к передаче данных через аргументы командной строки, либо через ввод со стороны пользователя. Такой подход оказался не применим для написания программ, использующих графический интерфейс. Для таких программ удобнее использовать асинхронный стиль, где на определённые события вызывается зарегистрированный обработчик (функция). Для реализации такого подхода используется цикл событий. Обычно операционная система предоставляет функцию выборки следующего сообщения (наподобие get_next_message(), и блокирует поток выполнения до тех пор пока, не появится хотя-бы одно сообщение. Таким образом, тело цикла выполняется только тогда, когда есть что обрабатывать. Если для данного сообщения зарегистрирован обработчик — он вызывается. Как правило долгие операции, такие как взаимодействие с сетью выполняются в других потоках выполнения, чтобы графический интерфейс оставался отзывчивым.

Цикл событий в псевдокоде:

function main
    initialize()
    while message != quit
        message := get_next_message()
        process_message(message)
    end while
end function


Асинхронный подход нашел применение и в сетевом программировании. Например, сервер nginx, работающий асинхронно и использующий неблокирующий ввод-вывод, способен обрабатывать гораздо большее количество соединений, нежели его синхронные аналоги, создающие по потоку или процессу на каждого клиента.

Реализации[править | править вики-текст]

Unix[править | править вики-текст]

В Unix, парадигма «всё это файл», естественно, приводит к циклу событий, в основе которого события связанные с файлами. Чтение и запись в файлы, межпроцессное взаимодействие, сети связи и управления устройствами, все это достигается с помощью файлового ввода/вывода, где файлы идентифицируются дескрипторами. Системные вызовы select и poll позволяют наблюдать за изменением состояния множества файловых дескрипторов, например, чтобы узнать когда данные становятся доступными для чтения/записи, ошибками и т. п. событиями связанными с файлами. Данные вызовы блокируют выполнение программы на определённое время, пока на одном из наблюдаемых файловых дескрипторов не появится запрашиваемое событие. Данные функции замедляют работу при большом числе файловых дескрипторов, в связи с этим у них есть более современные аналоги: epoll на Linux и kqueue на FreeBSD. Для всех этих вызовов следует использовать неблокирующие файловые дескрипторы.

Обработка сигналов[править | править вики-текст]

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

Таким образом, очевидный способ обрабатывать сигналы, для обработчиков сигналов, это установка глобального флага и включение проверки этого флага в цикл событий, непосредственно до и после вызова select(), и, если он установлен, обрабатывать сигнал таким же образом, как и события с помощью дескрипторов. К сожалению, это приводит к состоянию гонки: если сигнал поступает сразу между проверкой флага и вызовом select(), то он не будет обработан до тех пор, пока из select() не будет произведён возврат, по какой-либо другой причине (например, прерван разочарованным пользователем).

Решение, к которому пришли в POSIX, является pselect вызов, которого похож на select(), но имеет дополнительный sigmask, который описывает маску сигналов. Это позволяет приложению, замаскировать сигналы в основной задаче, а затем удалить маску в течение времени, пока управление находится в вызове select(), так что обработчики сигналов вызываются только в то время как приложение находится на границе ввода-вывода. Тем не менее, реализации pselect() только в последнее время стали надежными; версии до Linux 2.6.16 не имеют системного вызов pselect(), заставляя Glibc подражать ему помощью метода, склонному к тому-же состоянию гонки, во избежание которого и предназначен pselect().

Альтернативное, и более переносимое решение, это преобразовать асинхронные события в события на основе файлов, используя пайп-себе трюк,[4], в котором «обработчик сигнала пишет байт в пайп, другой конец которого наблюдается вызовом pselect() в основной программе».[5] В ядре Linux версии 2.6.22 был добавлен новый системный вызов signalfd(), который позволяет получать сигналы через специальный дескриптор файла.

Microsoft Windows[править | править вики-текст]

Помимо неблокирующего ввода-вывода, использующего такие функции мультиплексирования ввода-вывода, как WSAPoll или select, в операционной системе Microsoft Windows предусмотрен и асинхронный ввод-вывод. Для асинхронных операций ввода-вывода существует Overlapped I/O. Если в блокирующие функции, такие как ReadFile() или WriteFile(), передать одним из аргументов структуру OVERLAPPED, то эти функции, мгновенно вернут управление программе. Узнать о завершении операции можно с помощью callback функции или Input/output completion port (рус. порт завершения ввода-вывода).

Помимо ввода-вывода в Windows реализован цикл событий для графических приложений. «Сердцем» таких приложений является функция WinMain(), которая вызывает GetMessage() в цикле. GetMessage() блокируется, пока не поступит какое-либо событие (также есть PeekMessage(), как неблокирующая альтернатива). Далее после небольшой обработки вызывается DispatchMessage(), которая передаёт сообщение о событии надлежащему обработчику, также известному как WindowProc. Сообщения, для которых не зарегистрирован обработчик передаются обработчику по-умолчанию (DefWindowProc)

См. также[править | править вики-текст]

Примечания[править | править вики-текст]

  1. Ретабоуил, Сильвен. Android NDK. Разработка приложений под Android на С/С++. — М.: ДМК Пресс, 2012. — С. 190. — 496 с. — ISBN 978-5-94074-657-7.
  2. Будилов Вадим Анатольевич. Интернет-программирование на Java. — Петербург: БХВ, 2003. — С. 41. — 704 с.
  3. Ламот, Андре. Программирование трехмерных игр для Windows. Советы профессионала по трехмерной графике и растеризации (+ CD-ROM) = Tricks of the 3D Game Programming Gurus: Advanced 3D Graphics and Rasterization. — М.: Вильямс, 2006. — С. 73. — 1415 с. — ISBN 5-8459-0627-X, 0-672-31835-0.
  4. D. J. Bernstein. The self-pipe trick.
  5. BUGS, pselect(2): synchronous I/O multiplexing — страница справки man для разработчика Linux — системные вызовы  (англ.)