Как управляемые событиями операции ввода-вывода допускают многопроцессорность?
Мне известно о событиях, связанных с вводом-выводом, как select, poll, epoll и т.д., чтобы кто-то мог сказать, что это очень масштабируемый веб-сервер, но я смущен деталями. Если для сервера существует только один поток выполнения и один процесс, то когда сервер выполняет свою "обработку" для готовых клиентов, это не делается серийным способом для обработки списка готовых клиентов, поскольку он не могут быть запланированы на нескольких ядрах или процессорах? Более того, когда эта обработка происходит... не будет ли сервер не отвечать?
Раньше я думал, что люди использовали пулы потоков для обработки ввода-вывода событий на бэкэнд, но я был смущен, когда недавно услышал, что не все используют пулы потоков для своих приложений.
Ответы
Ответ 1
Хммм. Вы (оригинальный плакат) и другие ответы, я думаю, приходят к этому назад.
Вы, кажется, понимаете часть, управляемую событиями, но повеситесь на то, что происходит после того, как событие срабатывает.
Ключевое значение для понимания состоит в том, что веб-сервер обычно тратит очень мало времени на "обработку" запроса и на много времени ждет диск и сетевой ввод-вывод.
Когда приходит запрос, обычно есть одна из двух вещей, которые должен выполнять сервер. Либо загрузите файл и отправьте его клиенту, либо передайте запрос на что-то еще (классически, CGI script, в наши дни FastCGI чаще встречается по понятным причинам).
В любом случае задание сервера является вычислительно минимальным, это просто посредник между клиентом и диском или "что-то еще".
Вот почему эти серверы используют так называемый неблокирующий I/O.
Точные механизмы варьируются от одной операционной системы к другой, но ключевым моментом является то, что запрос на чтение или запись всегда возвращается мгновенно (или почти достаточно). Когда вы пытаетесь записать, например, в сокет, система либо сразу принимает то, что может, либо в буфер, либо возвращает что-то вроде ошибки EWOULDBLOCK, давая вам знать, что он не может брать больше данных прямо сейчас.
После того, как запись была принята, программа может записать состояние соединения (например, "5000 из 10000 байт отправлено" или что-то еще) и перейти к следующему соединению, которое готово к действию, приходящее назад к первому, после того как система готова взять больше данных.
Это не похоже на обычный блокирующий сокет, где большой запрос на запись может блокироваться довольно долго, поскольку ОС пытается отправить данные по сети клиенту.
В некотором смысле, это ничем не отличается от того, что вы можете делать с потоковым вводом-выводом, но оно значительно сокращает накладные расходы в виде памяти, переключение контекста и общее "ведение домашнего хозяйства" и максимально использует преимущества какие операционные системы лучше всего (или, как предполагается, все равно): быстро обрабатывают ввод-вывод.
Что касается многопроцессорных/многоядерных систем, применяются те же принципы. Этот стиль сервера по-прежнему очень эффективен для каждого отдельного процессора. Вам просто нужен тот, который разветкит несколько экземпляров, чтобы воспользоваться дополнительными процессорами.
Ответ 2
Некоторая часть этой мудрости предшествует общей доступности многоядерных систем. В многозадачной среде это все еще верно. Только за исключением портативной электроники большинство используемых вами машин являются многопроцессорными в эти дни. И даже это может не длиться долго.
В чистой многозадачной системе вся ОС - это переключение с одного задания на другое, поскольку они становятся исполняемыми (разблокированными). Управляемый событиями и неблокирующий IO просто делают то же самое в пользовательском пространстве.
Для определенных задач он может помогать многопроцессорной обработке. Уменьшая выход потоков и взаимоисключающий код, больше процессоров может запускать приложение для большего количества тактовых циклов.
Например, в среде IDE вы не хотите, чтобы он постоянно просматривал файловую систему для внешних изменений. Если вы уже давно, вы, вероятно, сталкиваетесь с этим раньше, и это раздражает/непродуктивно. Он тратит ресурсы и заставляет глобальные модели данных блокироваться/не реагировать во время обновлений. Настройка прослушивателя событий IO ( "смотреть" в каталоге) освобождает приложение от других действий, например, помогает вам писать код.
Ответ 3
Идея состоит в том, что поток обработки не должен ждать завершения всей клиентской беседы, прежде чем она сможет обслуживать другую. Для многих серверных приложений большая часть времени сервера тратится на IO. Несмотря на то, что только один поток обрабатывает все запросы, количество добавленных латентностей невелико, потому что сервер тратит большую часть своего времени на IO в любом случае, и в этой схеме ожидания ожидания ввода-вывода не мешают серверу отвечать на другой запрос. Такая компоновка на самом деле не помогает серверу приходится делать большие объемы обработки с ограниченным процессором.
Более масштабируемая настройка объединила бы как асинхронный IO, так и несколько потоков, в идеале имеющую один рабочий поток на один исполнительный блок, доступный и не тратящий время на сон в IO, если нет работы.
Ответ 4
Обычно у вас есть несколько вариантов, учитывая, как работают обычные операционные системы, их API и типичные языки программирования:
-
1 поток/процесс на клиента. Модель программирования проста, но она не масштабируется. В большинстве ОС переключение между тысячами потоков неэффективно.
-
Используйте несколько мультиплексных операций ввода-вывода - выберите /poll/epoll/etc. на unixes, некоторые более эффективны, чем другие. Модель programmin сложнее, в некоторых случаях очень сложно, если вам нужно заниматься блокировкой операций как частью выполняемой вами работы (например, вызвать базу данных или даже прочитать файл из файловой системы), но она может масштабироваться намного лучше, чем 1 поток обслуживает 1 клиент.
-
Гибридный подход, вы используете мультиплексированный IO и имеете рабочие потоки. Несколько потоков, связанных с I/O, несколько потоков, выполняющих фактическую работу, и вы настраиваете количество потоков в каждом, основываясь на том, что вы делаете. Это самый масштабируемый и обычно сложнее программировать.
То, что вы выбираете, в основном является компромиссом. Неважно, если вы делаете материал в серийном режиме, если это будет сделано достаточно быстро. И если вам не нужно масштабировать, и вам когда-нибудь понадобится обработать несколько десятков или, возможно, hundres не занятых клиентов, использование самого простого подхода имеет смысл. Если ваше приложение может легко обрабатывать в 10 раз текущую нагрузку в одном потоке с мультиплексированным IO, вам не нужно проходить через проблему и выполнять рабочие потоки и т.д.
Если ваш сервер действительно занят, тогда да - он будет казаться невосприимчивым. Но процессоры быстрые, вы можете буквально делать миллионы вещей в течение секунды. Поэтому, если вы выполняете мультиплексирование ввода-вывода, вы не тратите время на ожидание, вы тратите все свое время на фактическую работу, и если выполнение этой работы может быть выполнено за несколько миллисекунд, вы можете обслуживать множество клиентов с помощью одного нить. Службы ОС, используемые вашим приложением, например, забота о сети IO может свободно использовать другие ядра.
Ответ 5
не выполняется серийно, чтобы обрабатывать список готовых клиентов, поскольку он не может быть запланирован на нескольких ядрах или процессорах?
Управляемые событиями системы непрерывно мультиплексируются между источниками событий. Я не уверен, что вы подразумеваете под серийным номером, но да, read() s и write() s не запускаются параллельно, если это то, что вы имеете в виду, но read() s и write() s от/до разных клиентов смешиваются.
Кроме того, когда эта обработка происходит... не будет ли сервер не отвечать на запросы?
Копирование буфера из ядра в пользовательское пространство или наоборот (или, возможно, не выполнение копирования, см. sendfile() и splice()) не занимает много времени, поэтому оно не заметно. С другой стороны, обработка PHP/Perl/Python/Ruby/Java может занять много времени, но обычно она переносится на другой процесс, поэтому он выходит из процесса/процессов основного веб-сервера.
Если вы действительно хотите высокую производительность, у типичной архитектуры будет один процесс/поток на процессор, каждый из которых выполняет управляемый событиями IO и некоторые рабочие процессы, выполняющие PHP/Perl/Python/Ruby/Java/CGI/...
РЕДАКТИРОВАТЬ:
Некоторая пища для размышлений:
управляемые событиями системы и функциональные языки
совместная резьба a la GNU pth
больше о потоках и событиях
Нити состояния SGI: псевдо-потоки поверх управляемой событиями системы
protothreads: легкие беспорядочные потоки
Ответ 6
Ключевое значение, которое следует помнить здесь, состоит в том, что только один поток может выполняться на процессоре за один раз, но I/O не всегда нужен CPU. Когда поток блокируется для ввода-вывода, CPU освобождается, так что может выполняться другой поток. Кроме того, даже на одном процессорном ядре несколько потоков часто могут выполнять ввод-вывод одновременно (в зависимости от используемой дисковой системы).
Ответ 7
Когда событие "срабатывает", сигнал поднимается, который останавливает текущее выполнение и выполняет код обработчиков сигналов.
Довольно часто этот код обработки сигналов порождает новый поток/процесс, а затем возвращает (иногда вы увидите реализации с использованием вилок процесса вместо потоков).
Нижняя строка состоит в том, что без нескольких потоков у вас может быть иллюзия параллельного выполнения, но на самом деле она просто останавливается и запускает основной код, а затем обрабатывает обработчики сигналов.
В Visual Basic есть такие вещи, как DoEvents, которые позволят другим обработчикам событий выполнять свои действия. Это обычно используется как форма предварительного удержания перед основной работой (или на каждой итерации цикла), чтобы позволить графическому интерфейсу обновляться (или в вашем случае веб-сервера для запроса клиента для начала обработки) между любая другая работа.
Другим методом, который может помочь, является асинхронный ввод-вывод, который будет поднимать сигнал, когда передача будет выполнена (или просто обработана x сумма), все в одном потоке выполнения. Хотя вам нужно надеяться, что асинхронные библиотеки ввода-вывода, которые вы используете, поддерживают многоядерную обработку (или базовую операционную систему), чтобы получить преимущество от нескольких ядер в этом сценарии.
Ответ 8
Ключом к большинству иллюзий в жизни является скорость, подумайте об этом. С момента появления многоядерных процессоров иллюзия многопроцессорности была вокруг. Идея заключается в том, что если один процессор переключается между процессами достаточно быстро, вы не заметите (пока физическое оборудование не столкнется с проблемами). Если мы начнем с этого, вы увидите, что, соединяя его с трюком, подобным асинхронному вводу/выводу, вы можете имитировать параллельную/многопроцессорную обработку.