Ответ 1
Ваша путаница может возникнуть из-за недостаточной фокусировки на контуре событий. ясно, что у вас есть представление о том, как это работает, но, возможно, не полная картина.
Часть 1, Основы Event Loop
Когда вы вызываете метод use
, что происходит за кулисами, создается другой поток для прослушивания соединений.
Однако, когда приходит запрос, потому что мы находимся в другом потоке, чем двигатель V8 (и не можем напрямую ссылаться на функцию маршрута), сериализованный вызов функции добавляется в общий цикл событий, поскольку он будет называться позже, (цикл событий является бедным именем в этом контексте, поскольку он работает скорее как очередь или стек)
в конце js файла, V8 проверяет, есть ли какие-либо запущенные теги или сообщения в цикле событий. Если их нет, он выйдет из 0 (поэтому серверный код продолжает работать). Таким образом, первый момент времени для понимания заключается в том, что никакой запрос не будет обработан до тех пор, пока не будет достигнут синхронный конец js файла.
Если цикл событий был добавлен во время запуска процесса, каждый вызов функции в цикле событий будет обрабатываться один за другим, в полном объеме, синхронно.
Для простоты позвольте мне разложить ваш пример на нечто более выразительное.
function callback() {
setTimeout(function inner() {
console.log('hello inner!');
}, 0); // †
console.log('hello callback!');
}
setTimeout(callback, 0);
setTimeout(callback, 0);
† setTimeout
со временем 0 - это быстрый и простой способ поставить что-нибудь в цикл событий без каких-либо осложнений по таймеру, так как независимо от того, он всегда был как минимум 0 мс.
В этом примере вывод всегда будет:
hello callback!
hello callback!
hello inner!
hello inner!
Оба последовательных вызова callback
присоединяются к циклу событий до того, как любой из них будет вызван, гарантирован. Это происходит потому, что из цикла событий ничего не может быть вызвано до полного синхронного выполнения файла.
Может быть полезно подумать о выполнении вашего файла, как о первом в цикле событий. Поскольку каждый вызов из цикла событий может происходить только последовательно, это становится логическим следствием того, что во время его выполнения не может возникнуть другой вызов цикла событий; Только после его завершения может быть вызвана другая функция цикла события.
Часть 2, Внутренний Обратный звонок
Такая же логика применяется и к внутреннему обратному вызову, и может быть использована для объяснения причин, по которым программа никогда не будет выводиться:
hello callback!
hello inner!
hello callback!
hello inner!
Как и следовало ожидать.
По завершении выполнения файла в цикле событий будут выполняться 2 последовательных вызова функций, как для callback
. Поскольку цикл Event является FIFO (сначала первым, первым), сначала будет вызываться setTimeout
который появился первым.
Первое, что делает callback
, - это выполнить другой setTimeout
. Как и раньше, это добавит сериализованный вызов, на этот раз к inner
функции, в цикл событий. setTimeout
немедленно возвращается, и выполнение переходит к первому console.log
.
В это время цикл событий выглядит следующим образом:
1 [callback] (executing)
2 [callback] (next in line)
3 [inner] (just added by callback)
Возврат callback
является сигналом для цикла события, чтобы удалить этот вызов из себя. Это теперь оставляет 2 вещи в цикле событий: еще 1 вызов для callback
и 1 вызов для inner
.
callback
- следующая функция в строке, поэтому она будет вызываться следующим образом. Процесс повторяется. Вызов inner
элемента добавляется к циклу событий. console.log
печатает Hello Callback!
и мы закончили, удалив этот вызов callback
из цикла событий.
Это оставляет цикл событий с еще двумя функциями:
1 [inner] (next in line)
2 [inner] (added by most recent callback)
Ни одна из этих функций не путается с циклом событий, они выполняются один за другим; Второй, ожидающий возвращения первого. Затем, когда второй возвращается, цикл события остается пустым. Это связано с тем, что в настоящее время нет других потоков, запускаемых в конце процесса. выход 0.
Часть 3, относящаяся к оригинальному примеру
Первое, что случается в вашем примере, - это то, что в процессе создается поток, который создаст сервер, привязанный к определенному порту. Обратите внимание, что это происходит в прекомпилированном C++, а не в javascript, и не является отдельным процессом, его потоком в рамках одного процесса. см.: C++ Учебник по теме
Итак, всякий раз, когда приходит запрос, выполнение вашего исходного кода не будет нарушено. Вместо этого запросы входящего соединения будут открываться, удерживаться и присоединяться к циклу событий.
Функция use
- это шлюз для поиска событий для входящих запросов. Его слой абстракции, но для простоты, полезно придумать функцию use
как и для setTimeout
. Кроме того, вместо ожидания установленного количества времени он добавляет обратный вызов в цикл событий при входящих HTTP-запросах.
Итак, допустим, что на сервер поступают два запроса: T1 и T2. В вашем вопросе вы говорите, что они приходят одновременно, поскольку это технически невозможно, я собираюсь предположить, что они один за другим, с незначительным временем между ними.
Какой бы запрос ни был первым, сначала будет обработан вторичным потоком ранее. как только это соединение было открыто, оно добавлено к циклу событий, и мы переходим к следующему запросу и повторяем.
В любой момент после того, как первый запрос добавлен в цикл событий, V8 может начать выполнение обратного вызова use
.
быстро отчитаться о readImage
Поскольку его непонятно, является ли readImage
из определенной библиотеки, то, что вы написали или каким-либо другим, невозможно точно сказать, что он будет делать в этом случае. Есть только 2 возможности, так что вот они:
// in this example definition of readImage, its entirely
// synchronous, never using an alternate thread or the
// event loop
function readImage (path, callback) {
let image = fs.readFileSync(path);
callback(null, image);
// a definition like this will force the callback to
// fully return before readImage returns. This means
// means readImage will block any subsequent calls.
}
// in this alternate example definition its entirely
// asynchronous, and take advantage of fs' async
// callback.
function readImage (path, callback) {
fs.readFile(path, (err, data) => {
callback(err, data);
});
// a definition like this will force the readImage
// to immediately return, and allow exectution
// to continue.
}
В целях объяснения я буду работать в предположении, что readImage немедленно вернется, так как должны быть надлежащие асинхронные функции.
После запуска use
обратного вызова произойдет следующее:
- Первый журнал консоли будет печататься.
- readImage начнет рабочий поток и немедленно вернется.
- Второй журнал консоли будет печататься.
В течение всего этого, важно отметить, что эти операции происходят синхронно; Никакой другой вызов цикла события не может начаться, пока они не будут завершены. readImage может быть асинхронным, но называть его нет, обратный вызов и использование рабочего потока - это то, что делает его асинхронным.
После этого use
возвращается обратным вызов, следующий запрос, вероятно, уже закончила синтаксический анализ и был добавлен в цикл обработки событий, в то время как V8 был занят нашей консоль журналы и readImage вызова.
Таким образом, следующий use
обратного вызова вызывается, и повторяет тот же процесс: журнал, стартует readImage нить, снова войти, возвращение.
После этого момента прочитанные изображения (в зависимости от того, сколько времени они занимают), вероятно, уже получили то, что им нужно, и добавили обратный вызов в цикл событий. Таким образом, они будут выполняться следующим образом, в порядке того, какой из них сначала получил свои данные. Помните, что эти операции выполнялись в отдельных потоках, поэтому они происходили не только параллельно основному потоку javascript, но и параллельно друг другу, поэтому здесь не важно, какой из них был вызван первым, важно, какой из них был первым, и получил dibs в цикле событий.
Какое бы ни было выполнено первое чтение, первое будет выполнено. поэтому, не допуская ошибок, мы распечатаем консоль, а затем напишем ответ на соответствующий запрос, содержащийся в лексической области.
Когда эта отправка будет возвращена, начнется следующий обратный вызов readImage: консольный журнал и запись в ответ.
на этом этапе потоки readImage скончались, а цикл событий пуст, но поток, содержащий привязку к порту сервера, поддерживает процесс, ожидание чего-то еще, чтобы добавить в цикл событий, и цикл для продолжения.
Надеюсь, это поможет вам понять механику асинхронного характера примера, который вы предоставили