Как управляемый событиями ввод-вывод позволяет многопроцессорную обработку?

Я знаю об управляемом событиями вводе-выводе как выбор, опрос, epoll, и т.д. позволяю, кто-то для создания говорит, что хорошо масштабируемый веб-сервер, но я смущен деталями. Если существует только один поток выполнения и одного процесса, работающего за сервером, то, когда сервер выполняет свою стандартную программу "обработки" для готовых клиентов, разве это не сделано последовательным способом обработать список готовых клиентов, так как это не может быть запланировано на несколько ядер или CPU? Кроме того, когда эта обработка происходит... разве, сервер не был бы безразличен?

Я раньше думал, что это было причиной, люди использовали пулы потоков для обработки события I/O на бэкенде, но я был смущен, когда я недавно услышал, что не все используют пулы потоков для их приложений.

15
задан Boris Yeltz 12 July 2010 в 18:16
поделиться

8 ответов

Хммм... Вы (оригинальный плакат) и другие ответы, я думаю, подходите к этому с обратной стороны.

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

Главное понять, что веб-сервер обычно тратит очень мало времени на "обработку" запроса, и очень много времени на ожидание дискового и сетевого ввода-вывода.

Когда приходит запрос, серверу обычно нужно сделать одну из двух вещей. Либо загрузить файл и отправить его клиенту, либо передать запрос чему-то другому (классически - CGI-скрипту, в наши дни по понятным причинам чаще используется FastCGI).

В любом случае, работа сервера минимальна с вычислительной точки зрения, это просто посредник между клиентом и диском или "чем-то еще".

Вот почему эти серверы используют так называемый неблокирующий ввод-вывод.

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

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

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

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

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

9
ответ дан 1 December 2019 в 04:17
поделиться

Идея состоит в том, что потоку обработки не нужно ждать завершения разговора с клиентом целиком, прежде чем он сможет обслуживать другого. Для многих серверных приложений большая часть времени сервера тратится на ожидание ввода-вывода. Несмотря на то, что есть только один поток, обрабатывающий все запросы, добавленная задержка мала, потому что сервер все равно тратил большую часть своего времени на ожидание ввода-вывода, и в этой схеме ожидание ввода-вывода не мешает серверу отвечать на другой запрос. Такое расположение на самом деле не помогает, поскольку серверу приходится выполнять большие объемы обработки с ограничением ЦП.

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

2
ответ дан 1 December 2019 в 04:17
поделиться

Обычно у вас есть несколько вариантов, учитывая то, как работают распространенные операционные системы, их API и типичные языки программирования:

  • 1 поток/процесс на клиента. Эта модель программирования проста, но она не масштабируется. В большинстве ОС переключение между тысячами потоков неэффективно

  • Используйте некоторые средства мультиплексирования ввода/вывода - это select/poll/epoll/etc. в unix, некоторые из них более эффективны, чем другие. Модель programmin сложнее, в некоторых случаях очень сложна, если вам нужно иметь дело с блокирующими операциями как частью работы, которую вы выполняете (например, вызов базы данных или даже чтение файла из файловой системы), но она может масштабироваться намного лучше, чем когда 1 поток обслуживает 1 клиента.

  • Гибридный подход, вы используете мультиплексированный ввод-вывод и имеете рабочие потоки. Несколько потоков занимаются вводом-выводом, несколько потоков выполняют фактическую работу, и вы настраиваете количество потоков в каждом из них в зависимости от того, что вы делаете. Это самый масштабируемый вариант, но обычно самый сложный для программирования.

То, что вы выберете, - это, по сути, компромисс. Неважно, выполняете ли вы что-то последовательно, если это и так делается достаточно быстро. И если вам не нужно масштабироваться, и вам когда-либо понадобится обрабатывать несколько десятков или, возможно, сотен не занятых клиентов, использование самого простого подхода имеет смысл. Если ваше приложение может легко справиться с нагрузкой, в 10 раз превышающей текущую, в одном потоке с мультиплексированным вводом-выводом, вам не нужно идти на трудности и внедрять рабочие потоки и т.д.

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

2
ответ дан 1 December 2019 в 04:17
поделиться

разве это не делается последовательно для обработки списка готовых клиентов, поскольку это не может быть запланировано на нескольких ядрах или процессорах?

Системы, управляемые событиями, постоянно мультиплексируют между источниками событий. Я не уверен, что вы имеете в виду под последовательным, но да, чтение()и запись()не выполняются параллельно, если вы это имеете в виду, но чтение()и запись()от/к разным клиентам перемежаются.

Более того, когда происходит такая обработка... разве сервер не будет не реагировать?

Копирование буфера из ядра в пользовательское пространство или наоборот (или, возможно, отсутствие копирования, см. sendfile() и splice()) не занимает много времени, поэтому это незаметно. С другой стороны, обработка PHP/Perl/Python/Ruby/Java может занять много времени, но это обычно перегружается на другой процесс, так что это вне основного процесса/процессов веб-сервера.

Если вам действительно нужна высокая производительность, типичная архитектура будет иметь один процесс/поток на процессор, каждый из которых выполняет управляемый событиями ввод-вывод, и несколько рабочих процессов, выполняющих PHP/Perl/Python/Ruby/Java/CGI/...

EDIT:

Немного пищи для размышлений:
системы, управляемые событиями, и функциональные языки
кооперативная обработка потоков а-ля GNU pth
подробнее о потоках и событиях
потоки состояния SGI: псевдопотоки поверх событийно-управляемой системы
protothreads: облегченные потоки без стеков

1
ответ дан 1 December 2019 в 04:17
поделиться

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

0
ответ дан 1 December 2019 в 04:17
поделиться

Главное здесь помнить, что только один поток может выполняться на CPU одновременно, но для ввода/вывода не всегда нужен CPU. Когда поток блокирует ввод-вывод, CPU освобождается, чтобы другой поток мог его выполнить. Кроме того, даже на одном процессоре несколько потоков могут выполнять ввод-вывод одновременно (в зависимости от используемой дисковой системы).

0
ответ дан 1 December 2019 в 04:17
поделиться

Когда событие «запускает», генерируется сигнал, который останавливает текущее выполнение и выполняет код обработчиков сигналов.

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

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

В Visual Basic есть такие вещи, как, например, DoEvents, которые позволяют другим обработчикам событий выполнять свои действия. Это обычно используется как форма упреждения перед основной работой (или на каждой итерации цикла), чтобы позволить обновлять графический интерфейс (или, в вашем случае, веб-сервер для начала обработки клиентского запроса) между любая другая работа.

Другой способ, который может помочь, - это асинхронный ввод-вывод, который будет вызывать сигнал, когда передача будет выполнена (или просто обработано x количество), и все это в одном потоке выполнения. Хотя вам придется надеяться, что библиотеки асинхронного ввода-вывода, которые вы используете, поддерживают многоядерную обработку (или базовую операционную систему), чтобы получить преимущество нескольких ядер в этом сценарии.

0
ответ дан 1 December 2019 в 04:17
поделиться

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

В чистой многозадачной системе все, что делает ОС, - это переключается с одного задания на другое по мере того, как они становятся работоспособными (разблокированными). Управляемый событиями и неблокирующий ввод-вывод делают то же самое в пользовательском пространстве.

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

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

2
ответ дан 1 December 2019 в 04:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: