Зачем нам нужен асинхронный обратный вызов в узле JS, поскольку Цикл событий предлагает Рабочий пул для обработки дорогостоящей задачи?

#node.js #async-await #event-loop #single-threaded #worker-pool

Вопрос:

Я изучал, как Node JS повышает производительность для нескольких одновременных запросов! Прочитав пару блогов, я узнал, что:

  1. При поступлении любого запроса запускается событие, и соответствующая функция обратного вызова помещается в очередь событий.
  2. Цикл событий(Основной поток) отвечает за обработку всех запросов в очереди событий. Цикл событий обрабатывает запрос и отправляет ответ, если запрос использует неблокирующий ввод-вывод.
  3. Если запрос содержит Блокирующий цикл событий ввода-вывода, он внутренне назначает этот запрос простаивающему работнику из рабочего пула, и когда работник отправляет ответ, цикл событий результата отправляет ответ.

Мой вопрос в том, что, поскольку цикл событий передает тяжелую блокирующую работу внутренне в рабочий пул с использованием библиотеки libuv, зачем нам нужен асинхронный обратный вызов?

Для лучшего понимания, пожалуйста, ознакомьтесь с приведенным ниже кодом:

 const express = require('express')
const app = express()
const port = 3000

function readUserSync(miliseconds) {
    var currentTime = new Date().getTime();
 
    while (currentTime   miliseconds >= new Date().getTime()) {
    }

    return "User"
 }

 async function readUserAsync(miliseconds) {
    var currentTime = new Date().getTime();
 
    while (currentTime   miliseconds >= new Date().getTime()) {
    }

    return "User"
 }

app.get('/sync', (req, res) => {
    const user = readUserSync(80)
    res.send(user)
})

  app.get('/async', async (req, res) => {
    const user = await readUserAsync(80)
    res.send(user)
  })

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

 

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

 ab -c 10 -t 5 "http://127.0.0.1:3000/async/"
ab -c 10 -t 5 "http://127.0.0.1:3000/sync/"

 

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

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

Комментарии:

1. В чем именно разница? Они должны быть довольно близко друг к другу, да?

2. Блокировка, отнимающая много времени кода внутри async функции, никому не приносит пользы. Это все еще просто блокирующий, отнимающий много времени код, независимо от того, находится ли он в async функции или в обычной функции. В случае, если у вас было какое-то ошибочное представление о том, что Javascript в async функциях выполняется в другом потоке, это НЕ так. Они выполняются в одном потоке, поэтому блокировка кода Javascript в async по-прежнему блокирует цикл событий.

3. @jfriend00 Предполагает readUserSync и readUserAsync выполняет операции ввода-вывода(например, получение пользователя из базы данных mysql), тогда каков будет сценарий для двух конечных точек?

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