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

Мне известны управляемые событиями операции ввода-вывода, такие как select, poll, epoll и т. Д., Которые позволяют кому-то создавать, скажем, хорошо масштабируемый веб-сервер, но меня смущают детали. Если для сервера существует только один поток выполнения и один процесс, то когда сервер выполняет свою подпрограмму «обработки» для готовых клиентов, разве это не выполняется последовательно для обработки списка готовых клиентов, поскольку нельзя запланировать на нескольких ядрах или процессорах? Более того, когда происходит такая обработка ... разве сервер не отвечает?

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

Ответы [ 8 ]

9 голосов
/ 12 июля 2010

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

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

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

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

В любом случае работа сервераминимально в вычислительном отношении, это просто посредник между клиентом и диском или «что-то еще».

Вот почему эти серверы используют то, что называется неблокирующим I / O.

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

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

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

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

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

2 голосов
/ 12 июля 2010

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

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

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

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

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

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

2 голосов
/ 12 июля 2010

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

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

2 голосов
/ 12 июля 2010

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

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

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

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

1 голос
/ 12 июля 2010

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

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

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

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

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

EDIT:

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

0 голосов
/ 12 июля 2010

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

0 голосов
/ 12 июля 2010

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

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

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

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

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

0 голосов
/ 12 июля 2010

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

...