Я только что посмотрел следующее видео: Введение в Node.js и до сих пор не понимаю, как вы получаете преимущества в скорости.
Главным образом, в какой-то момент Райан Даль (создатель Node.js) говорит, что Node.js основан на циклах событий, а не на потоках. Потоки дороги и должны быть оставлены на усмотрение только экспертов по параллельному программированию.
Позже он затем показывает стек архитектуры Node.js, который имеет базовую реализацию C, которая имеет свой собственный внутренний поток. Очевидно, что разработчики Node.js никогда не запускают свои собственные потоки и не используют пул потоков напрямую ... они используют асинхронные обратные вызовы. Это я понимаю.
Что я не понимаю, так это то, что Node.js все еще использует потоки ... он просто скрывает реализацию, так как это быстрее, если 50 человек запрашивают 50 файлов (в данный момент не в памяти), тогда не требуется 50 потоков ?
Единственное отличие состоит в том, что, поскольку он управляется изнутри, разработчику Node.js не нужно кодировать многопоточные детали, но он все еще использует потоки для обработки запросов файлов IO (блокирования).
Так разве вы не просто берете одну проблему (многопоточность) и скрываете ее, пока эта проблема еще существует: главным образом, несколько потоков, переключение контекста, мертвые блокировки ... и т. Д.?
Должна быть какая-то деталь, которую я до сих пор здесь не понимаю.
источник
select()
происходит быстрее, чем обмен контекста потока.Ответы:
Здесь на самом деле объединяются несколько разных вещей. Но это начинается с мема, что темы просто очень сложны. Поэтому, если они сложны, вы, скорее всего, при использовании потоков 1) нарушаете работу из-за ошибок и 2) не используете их максимально эффективно. (2) это тот, о котором вы спрашиваете.
Подумайте об одном из примеров, которые он приводит, когда приходит запрос, вы запускаете какой-то запрос, а затем что-то делаете с результатами этого. Если вы напишите это стандартным процедурным способом, код может выглядеть так:
Если поступивший запрос заставил вас создать новый поток, который выполнял приведенный выше код, у вас будет поток, который будет ничего не делать, пока
query()
работы. (По словам Райана, Apache использует один поток для удовлетворения исходного запроса, тогда как nginx превосходит его в тех случаях, о которых он говорит, потому что это не так.)Теперь, если вы действительно умны, вы бы выразили приведенный выше код таким образом, чтобы среда могла работать и делать что-то еще во время выполнения запроса:
Это в основном то, что делает node.js. Вы в основном декорируете - таким образом, который удобен из-за языка и среды, а следовательно, и из-за замыканий, - вашего кода таким образом, что среда может быть умной в отношении того, что и когда выполняется. Таким образом, файл node.js не нов в том смысле, что он изобрел асинхронный ввод-вывод (не то, чтобы кто-то утверждал что-либо подобное), но он нов в том, что способ его выражения немного отличается.
Примечание: когда я говорю, что среда может быть умной в отношении того, что и когда выполняется, в частности, я имею в виду, что поток, который она использовала для запуска некоторого ввода-вывода, теперь можно использовать для обработки какого-либо другого запроса или выполнения некоторых вычислений. параллельно, или запустите другой параллельный ввод / вывод. (Я не уверен, что узел достаточно сложен, чтобы начать больше работы для того же запроса, но вы поняли.)
источник
Заметка! Это старый ответ. Хотя это все еще верно в общих чертах, некоторые детали могли измениться из-за быстрого развития Node в последние несколько лет.
Он использует потоки, потому что:
Для подделки неблокирующего ввода-вывода необходимы потоки: блокируйте ввод-вывод в отдельном потоке. Это уродливое решение и вызывает много накладных расходов.
Еще хуже на аппаратном уровне:
Это просто глупо и неэффективно. Но это работает по крайней мере! Мы можем наслаждаться Node.js, потому что он скрывает уродливые и громоздкие детали за асинхронной архитектурой, управляемой событиями.
Может быть, кто-то будет реализовывать O_NONBLOCK для файлов в будущем? ...
Редактировать: я обсуждал это с другом, и он сказал мне, что альтернативой потокам является опрос с помощью select : укажите время ожидания 0 и выполните IO для возвращенных дескрипторов файлов (теперь они гарантированно не блокируются).
источник
Я боюсь, что я "делаю не то" здесь, если так, удалите меня, и я извиняюсь. В частности, я не вижу, как я создаю аккуратные маленькие аннотации, которые создали некоторые люди. Тем не менее, у меня есть много проблем / замечаний, чтобы сделать в этой теме.
1) Закомментированный элемент в псевдокоде в одном из популярных ответов
по сути фальшивая. Если поток вычисляет, то он не крутит большие пальцы, он выполняет необходимую работу. Если, с другой стороны, он просто ожидает завершения ввода-вывода, то он не использует процессорное время, весь смысл инфраструктуры управления потоками в ядре состоит в том, что процессор найдет что-то полезное для выполнения. Единственный способ «перевернуть свои пальцы», как предлагается здесь, - создать цикл опроса, и никто, кто закодировал реальный веб-сервер, не настолько неспособен сделать это.
2) «Потоки сложны», имеет смысл только в контексте обмена данными. Если у вас есть по существу независимые потоки, как, например, в случае обработки независимых веб-запросов, то создание потоков является тривиально простым, вы просто кодируете линейный поток обработки одной работы и сидите, зная, что она будет обрабатывать несколько запросов, и каждый будет эффективно независимым. Лично я бы рискнул, что для большинства программистов изучение механизма закрытия / обратного вызова является более сложным, чем простое кодирование версии потока сверху вниз. (Но да, если вам нужно общаться между потоками, жизнь становится действительно тяжелой и очень быстрой, но тогда я не уверен, что механизм закрытия / обратного вызова действительно меняет это, он просто ограничивает ваши варианты, потому что этот подход все еще достижим с потоками Во всяком случае, это
3) До сих пор никто не представил никаких реальных доказательств того, почему один конкретный тип переключения контекста будет более или менее трудоемким, чем любой другой тип. Мой опыт создания многозадачных ядер (в небольшом масштабе для встроенных контроллеров, ничего более изящного, чем «настоящая» ОС) подсказывает, что это не так.
4) Все иллюстрации, которые я до сих пор видел, предназначенные для того, чтобы показать, насколько быстрее Node, чем у других веб-серверов, ужасно ошибочны, однако они ошибочны таким образом, что косвенно иллюстрируют одно преимущество, которое я определенно принял бы для Node (и это отнюдь не незначительно). Узел не выглядит так, как будто он нуждается (и даже не разрешает) в настройке. Если у вас есть многопоточная модель, вам нужно создать достаточное количество потоков для обработки ожидаемой нагрузки. Сделайте это плохо, и вы получите плохую производительность. Если потоков слишком мало, то процессор простаивает, но не может принимать больше запросов, создавать слишком много потоков, и вы будете тратить впустую память ядра, а в случае среды Java вы также будете тратить впустую память основной кучи , Теперь для Java тратить кучу - это первый, лучший способ поднять производительность системы, потому что эффективная сборка мусора (в настоящее время это может измениться с G1, но кажется, что жюри все еще находится на этом этапе по крайней мере в начале 2013 года) зависит от наличия большого количества свободной кучи. Итак, есть проблема: настройте его на слишком малое количество потоков, у вас незанятые процессоры и низкая пропускная способность, настройте его на слишком много, и он застрянет другими способами.
5) Есть другой способ, которым я принимаю логику утверждения, что подход Node «быстрее по замыслу», и это - это. В большинстве моделей потоков используется модель переключения контекста с разделением по времени, расположенная поверх более подходящей модели (предупреждение о значении :) и более эффективной модели (не оценка значения). Это происходит по двум причинам: во-первых, большинство программистов, по-видимому, не понимают приоритетное вытеснение, и, во-вторых, если вы изучаете многопоточность в среде Windows, существует временная привязка, нравится вам это или нет (конечно, это подтверждает первый пункт. Примечательно, что в первых версиях Java использовалось приоритетное вытеснение в реализациях Solaris и временная привязка в Windows. Поскольку большинство программистов не понимали и жаловались, что «многопоточность не работает в Solaris» они поменяли модель на временную шкалу везде). В любом случае, суть в том, что временная привязка создает дополнительные (и потенциально ненужные) переключатели контекста. Каждое переключение контекста отнимает процессорное время, и это время эффективно удаляется из работы, которую можно выполнить в реальной работе под рукой. Однако время, затрачиваемое на переключение контекста из-за временного среза, не должно превышать очень небольшой процент от общего времени, если только не происходит что-то довольно странное, и я не вижу причин, чтобы ожидать, что это произойдет в простой веб-сервер). Итак, да, избыточные переключатели контекста, вовлеченные во временную привязку, неэффективны (и это не происходит в и это время эффективно отводится от работы, которую можно выполнить на реальной работе под рукой. Однако время, затрачиваемое на переключение контекста из-за временного среза, не должно превышать очень небольшой процент от общего времени, если только не происходит что-то довольно странное, и я не вижу причин, чтобы ожидать, что это произойдет в простой веб-сервер). Итак, да, избыточные переключатели контекста, вовлеченные во временную привязку, неэффективны (и это не происходит в и это время эффективно отводится от работы, которую можно выполнить на реальной работе под рукой. Однако время, затрачиваемое на переключение контекста из-за временного среза, не должно превышать очень небольшой процент от общего времени, если только не происходит что-то довольно странное, и я не вижу причин, чтобы ожидать, что это произойдет в простой веб-сервер). Итак, да, избыточные переключатели контекста, вовлеченные во временную привязку, неэффективны (и это не происходит впотоки ядра, как правило, кстати, но разница будет в нескольких процентах пропускной способности, а не в виде целых числовых факторов, которые подразумеваются в заявках на производительность, которые часто подразумеваются для Node.
В любом случае, извинения за то, что все это было длинным и грубым, но я действительно чувствую, что обсуждение пока ничего не доказало, и я был бы рад услышать от кого-то в любой из этих ситуаций:
а) реальное объяснение того, почему Node должен быть лучше (помимо двух сценариев, которые я изложил выше, первый из которых (плохая настройка), я считаю, является реальным объяснением всех тестов, которые я видел до сих пор. ([править ], на самом деле, чем больше я об этом думаю, тем больше мне интересно, может ли быть здесь важна память, используемая огромным количеством стеков. Размеры стеков по умолчанию для современных потоков, как правило, довольно велики, но память, выделяемая система событий на основе замыканий будет только тем, что нужно)
б) настоящий эталонный тест, который фактически дает реальную возможность для выбранного многопоточного сервера. По крайней мере, таким образом, я должен был бы перестать верить, что утверждения по сути ложные;> ([править] это, вероятно, гораздо сильнее, чем я предполагал, но я чувствую, что объяснения, приведенные для повышения производительности, в лучшем случае неполны, и показанные критерии являются необоснованными).
Ура, Тоби
источник
open()
нельзя сделать неблокирующим?). Таким образом, амортизируется любое снижение производительности, когда традиционная модельfork()
/pthread_create()
-по запросу должна создавать и уничтожать потоки. И, как упоминалось в постскриптуме а), это также амортизирует проблему стекового пространства. Вы можете, вероятно, обслуживать тысячи запросов, скажем, с 16 потоками ввода-вывода.Райан использует потоки для тех частей, которые блокируют (большинство из node.js использует неблокирующие операции ввода-вывода), потому что некоторые части безумно трудно писать без блокировки. Но я верю, что Райан хочет, чтобы все было неблокирующим. На слайде 63 (внутренний дизайн) вы видите, что Райан использует libev (библиотеку, которая абстрагирует уведомления об асинхронных событиях) для неблокирующей блокировки событий . Из-за цикла обработки событий node.js требует меньших потоков, что уменьшает переключение контекста, потребление памяти и т. Д.
источник
Потоки используются только для работы с функциями, не имеющими асинхронных возможностей, например
stat()
.stat()
Функция всегда блокирует, поэтому node.js потребности использовать нить для выполнения фактического вызова без блокировки основного потока (цикл событий). Потенциально никакой поток из пула потоков никогда не будет использоваться, если вам не нужно вызывать такие функции.источник
Я ничего не знаю о внутренней работе node.js, но я вижу, как использование цикла обработки событий может превзойти потоковую обработку ввода-вывода. Представьте запрос диска, дайте мне staticFile.x, сделайте 100 запросов на этот файл. Каждый запрос обычно занимает поток, получающий этот файл, то есть 100 потоков.
Теперь представьте, что первый запрос создает один поток, который становится объектом публикатора. Все 99 других запросов сначала смотрят, существует ли объект публикатора для staticFile.x, если так, слушайте его, пока он работает, в противном случае запускайте новый поток и, таким образом, новый объект издателя.
Как только один поток завершен, он передает staticFile.x всем 100 слушателям и уничтожает себя, поэтому следующий запрос создает новый новый поток и объект издателя.
Таким образом, в приведенном выше примере это 100 потоков против 1 потока, а также 1 поиск диска вместо 100 поиска диска, усиление может быть весьма феноменальным. Райан умный парень!
Другой способ взглянуть на это один из его примеров в начале фильма. Вместо того:
Опять же, 100 отдельных запросов к базе данных против ...:
Если запрос уже выполняется, другие равные запросы просто запрыгнут на подножку, так что вы можете иметь 100 запросов за одну поездку в одну базу данных.
источник