Неблокирующий ввод/вывод был описан в разделах 5.9 и 44.9. Если поместить файловый дескриптор в неблокирующий режим, указав флаг состояния открытого файла O_NONBLOCK, то системные вызовы, которые не могут завершиться немедленно, не блокируются, а возвращают ошибку. Этот подход можно применять к именованным каналам, очередям FIFO, сокетам, терминалам, псевдотерминалам и ряду других видов устройств.

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

Термин «опрашивать» (англ. poll), который мы используем в этой главе, относится как к системному вызову poll(), предназначенному для мультиплексированного ввода/вывода, так и к процедуре «неблокирующей проверки состояния файлового дескриптора».

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

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

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

• Мультиплексированный ввод/вывод позволяет процессу отслеживать сразу несколько файловых дескрипторов и определять, возможно ли выполнять операции чтения или записи с каким-либо из них. Для этого предусмотрены системные вызовы select() и poll().

Ввод/вывод на основе сигналов — методика, согласно которой процесс просит ядро отправить ему сигнал, когда в заданном дескрипторе станет доступным ввод или в него можно будет записать какие-нибудь данные. Далее процесс может перейти к выполнению каких-то других задач; о возможности ввода/вывода он будет уведомлен с помощью сигнала. При мониторинге большого количества файловых дескрипторов данный подход демонстрирует намного лучшую производительность, чем вызовы select() и poll().

• Программный интерфейс epoll поддерживается только в Linux версии 2.6 и выше. По аналогии с мультиплексированным вводом/выводом он позволяет следить за множеством файловых дескрипторов и проверять, допускают ли они чтение или запись. Как и ввод/вывод на основе сигналов, интерфейс epoll имеет значительно лучшую производительность при работе с большим количеством файловых дескрипторов.

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

Все три методики, описанные выше, направлены на достижение одного и того же результата: одновременного мониторинга одного или (чаще всего) нескольких файловых дескрипторов, чтобы проверить, готовы ли они к выполнению ввода/вывода (или, если быть точным, можно ли к ним применить системные вызовы чтения или записи без блокировки). Файловый дескриптор становится готовым в результате какого-то события, например появления ввода, завершения соединения с сокетом или освобождения места в ранее заполненном исходящем буфере сокета после передачи отложенных данных удаленной стороне. Наблюдение за множеством файловых дескрипторов может пригодиться в таких приложениях, как сетевые серверы, которые занимаются мониторингом сразу нескольких клиентских сокетов или вынуждены следить за вводом одновременно с терминала и именованного канала (или сокета).

Перейти на страницу:

Похожие книги