如何理解Node.js的单线程?

我们常说, Node.js 是单线程的,这句话对新人有很大的误导作用。首先要明确:Node.js 程序并非「单线程」,证明代码如下:

let end = Date.now() + 1000 * 10
for (; Date.now() < end;) {}

运行之后,到活动监视器中搜索 Node,结果如下:

活动监视器中搜索 Node
看到没,一个 Node 程序有 7 个线程。到这里,你可能会很困惑,这究竟是怎么回事?其实正确的说法应该是:

单线程的指的是 JavaScript 的执行是单线程的,但 Javascript 的宿主环境并非单线程。

怎么理解这句话呢?其实当你用 node xxx.js 运行程序的时候,操作系统会启动下面 7 个线程:

  • 1 个 Javascript 主线程用于执行用户代码
  • 1 个 watchdog 监控线程用于处理调试信息
  • 1 个 v8 task scheduler 线程用于调度任务优先级,加速延迟敏感任务执行
  • 4 个 v8 线程用来执行代码调优与 GC 等后台任务

所以说,我们的 Node 程序中包含的线程实际上是:JavaScript 的宿主环境需要的线程 + JavaScript 的执行线程。

到这里是不是有种豁然开朗的感觉了?别急,我再加一行代码:

require('fs').readFile(require.main.filename, () => {})
let end = Date.now() + 1000 * 10
for (; Date.now() < end;) {}

这个时候活动监视器的结果如下:

如何理解 Node.js 的单线程?

WTF?怎么多了 4 个线程?这是怎么回事?其实原因就出现在第一行代码上:

require('fs').readFile(require.main.filename, () => {})

这行代码是异步操作,而 Node 是「异步非阻塞」的语言,如果还是 7 个线程的话,就意味着 JavaScript 主线程也要执行 I/O 操作,从而造成了阻塞。所以 libuv 创建了线程池,默认情况下线程池里面有 4 个线程,所以我们看到的结果是 11。

也就是说,代码里面那些异步操作,全部由线程池来接管,JavaScript 主线程不参与进去,只是执行同步代码而已。下面是 Node 进程结构图:

Node 进程结构图
如果全部是同步代码,那么只会开启 7 个线程,如果存在异步 I/O 操作,则默认会开启 11 个线程。为什么说是「默认」呢?因为 uv_thread_pool 的容量是可以改的,只要设置环境变量 UV_THREADPOOL_SIZE 即可。例如下面的代码把线程池的容量改成 1:

process.env.UV_THREADPOOL_SIZE = 1
require('fs').readFile(require.main.filename, () => {})
let end = Date.now() + 1000 * 10
for (; Date.now() < end;) {}

这个时候就只有 8 个线程了:

如何理解 Node.js 的单线程?
所以,Node.js 程序并非单线程,只不过主线程是单线程的,所有的异步 I/O 操作由 libuv 的线程池中的线程进行处理,然后把运行结果通过回调的方式通知到主线程。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 如何理解Node.js的单线程?

发表回复