Skip to content

事件循环

向军大叔每晚八点在 抖音bilibli 直播

xj-small

一般的服务器会为每个用户请求单独生成线程来服务,但线程的管理是比较耗时的。Node.js本身是以单线程的模式运行的,即所有用户请求使用一个主线程完成,但这种方式如果多个用户请求时会发生阻塞,请求会排队执行。

为了解决这个问题,Node 内部使用 Libuv 实现异步非阻塞的任务处理。

  • libuv是使用C语言编写的跨平台开源库
  • libuv是处理Node.js中异步非阻塞操作的
  • libuv使用线程池和事件循环处理异步操作
image-20220828001313537

Node 主线程维护事件队列,接收到请求后放入事件队列中,然后继承接收其他请求。

主线程空闲时,循环事件队列,检查队列中是否有任务,这时根据不同的任务使用不同的处理方式

  • 如果是非I/O 操作,就通过主线程执行,然后通过回调函数返回到上层调用
  • 如果是 I/O 操作就交给 Libuv 使用多线程处理,处理后再以回调函数形式返回到事件队列中(执行上步操作)

简单总结:

非 IO 操作:接收请求-> 放入任务队列->事件循环->返回上传调用

I/O 操作:接收请求-> 放入任务队列->Libuv 线程池处理->返回任务队列->事件循环->返回上传调用

线程池

node.js中以Sync为后缀的方法,一般为同步方法。同步方法会在node.js主线程中执行,会发生阻塞。

import { pbkdf2Sync } from 'crypto'

const begin = Date.now()
const key1 = pbkdf2Sync('houdunren.com', 'salt', 100000, 64, 'sha512')
const key2 = pbkdf2Sync('houdunren.com', 'salt', 100000, 64, 'sha512')
const key3 = pbkdf2Sync('houdunren.com', 'salt', 100000, 64, 'sha512')
console.log(Date.now() - begin)

下面将crypto加密操作使用异步执行,这时node.js 主线程会将任务交给libuv线程池执行,不阻塞主线程。因为libuv是多线程,这时执行时间就少很多。当libuv执行完后调用node.js主线程中的回调函数。

import { pbkdf2, pbkdf2Sync } from 'crypto'

const begin = Date.now()
for (let i = 0; i < 3; i++) {
  pbkdf2('houdunren.com', 'salt', 100000, 64, 'sha512', () => {
    console.log(`${i} - `, Date.now() - begin)
  })
}

回调函数

下面通过文件写入的例子,说明回调函数的工作机制,并不会真正写入文件。主要是帮助大家对上面的事件循环加深理解。

function writeFile(file: string, content: string, callback: (error: any) => void) {
	//执行到这里,放入异步队列
  setTimeout(() => {
    try {
      console.log('%s 文件写入成功,内容是 %s', file, content)
      //任务执行后,调用回调函数
      callback(null)
    } catch (error: any) {
      callback(error)
    }
  }, 2000)
}

writeFile('hd.txt', 'houdunren.com', (error) => {
  error ? console.log(error) : console.log('文件保存成功')
})

Promise

使用 Promise 与 Async 与 Await 可以使用异步的编写更清晰,并避免回调函数带来的嵌套问题。

有关于 Promise的内容 向军大叔已经录制了视频,大家可以访问 后盾人 学习。