NodeJS 中的阻塞与非阻塞

Blocking vs Non-Blocking in NodeJS

我们都知道 NodeJS 是单线程的,这意味着如果我们的代码中有一个 async/await 操作,节点将在执行其余代码之前等待它完成。因此,如果用户发出异步请求,其他用户也应该在发出请求之前等待它完成吗?

这里我创建了一个简单的例子,第一条路由使用异步函数,发送响应需要 10 秒,第二条路由立即发送响应。

当我向第一个路由发送请求并等待响应时,我向第二个路由发送了另一个请求,即使第一个路由尚未完成代码执行,我也收到了响应。

为什么这个例子是非阻塞的?

function sleep(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve(true)
    },10000)
  }).then(val=>val)
}


router.get('/route1',async (req,res)=>{
  const test = await sleep() 
  res.send('HELLO WORLD')
})

router.get('/route2',(req,res)=>{
  res.send("HELLO WORLD")
})

await 仅 blocks/suspends 执行当前函数,而不是整个解释器。事实上,当一个函数命中函数内部的第一个 await 时,该函数立即 returns 承诺和该函数之后的其他处理(或发生的其他事件)可以自由 运行.

因此,在您的示例中,当它到达 await sleep() 时,该函数的执行将暂停,直到 await resolves/rejects 和包含的 async 函数立即 returns 一个未实现的承诺。由于带有 router.get() 的 Express 没有对返回的承诺做任何事情,它只是忽略它并且 returns 控制回到事件循环。稍后,您的第二个请求到达服务器,一个事件被放入 nodejs 事件队列,Express 被调用并为您的第二个路由处理程序提供服务。

so if a user make an async request other users should wait for it to be done before making requests too?

没有。只有包含 await 的那个请求处理程序的一个实例被挂起。解释器中的其他执行和事件循环中的其他事件处理程序(例如其他传入请求)仍然可以发生,因此仍然可以处理其他请求,即使一个请求处理程序位于 await。这说明了 await 如何不阻塞或暂停整个解释器,只执行一个函数。

When I sent a request to first route and while waiting for a response I sent another request to the second route and I got a response even though the first route didn't finish executing the code yet. why is it non-blocking on this example?

只有第一条路线被 await 暂停。其他事件和其他传入请求仍然可以正常处理。

NodeJs 不是单线程。 您可以通过以下步骤进行检查:

  • 创建一个这个 js 文件,然后 运行 它:

    while(true)

  • 在终端上执行此命令以获取使用 运行 此 js 文件的线程数。

    NUM=ps M <pid> | wc -l && 回显线程数为:$((NUM-1))

并且您可以看到 while(true) 使用了超过 1 个线程。

让我们回到您的代码, 之所以在/route1的请求还没有完成的时候,可以立即得到/route2的请求结果,是因为NodeJs使用EventLoop使得异步函数不会阻塞主线程。 当您调用睡眠函数时,NodeJs 将启动一个计时器,然后将您的回调从调用堆栈中取出(这就是为什么对 /route2 的请求不会被 /route1 阻塞),并且当您的计时器结束时resolve(true) 将被放入 EventQueue,在 EventLoop 的帮助下,您对 route1 的回调将被执行。

Link To EventLoop, Timer