关于JavaScript 与Nodejs(or浏览器) 线程

66 阅读3分钟

“JavaScript是单线程语言”,在学习JavaScript、Nodejs或浏览器的时候时常会看到类似的概念,同时,也有看到 web workers 或 worker threads。关于 JavaScript 体系的单线程与多线程似乎又没那么简单了

JavaScript

为什么说JavaScript 是单线程的(语言)?

从语言的语法层面来看,JavaScript的语法规范如ECMAScript 并没有对多线程实现的相关语法约定。而多线程语言,如go、rust等在语言层面一般有多线程设计、管理相关语法实现,如go的goruntine、rust的std::threads 等。所以JavaScript是单线程语言。

或者有这样的疑问:web workers或worker threads 不是多线程的吗,这是否可以说明JavaScript是有多线程语法的?web workers 是HTML5规范,由浏览器实现,HMLT5的DOM和BOM 语法与JavaScript规范并没直接关系,就像Ajax API、Fetch API一样。类似的 worker threads是Nodejs的多线程实现。

从语言设计角度来说,JavaScript设计之初主要目的之一是为了在浏览器里面监听用户的点击事件,执行对应的回调处理函数,采用多线程设计的话,会增加很多不必要的复杂度。

Nodejs

关于 Nodejs 是单线程还是多线程。可以跑下 Node 程序看看。简单的运行 Node(没有启动node http server)。

在node v9.0.0版本,观察到有9个线程

node v12.0.0 有10个线程

可以看到,Nodejs启动时有多个线程的。所以Nodejs单线程还是多线程,应该从不同角度理解。Nodejs 启动的 server 是单线程的(在不考虑 worker threads 的情况下,只有执行 JavaScript 代码的主线程),这也是通常说 Nodejs 单线程。但从 Nodejs 整体来看, 是多线程的。

从 Nodejs 的架构图可知,Nodejs 启动后,至少有以下几个线程:

➢ nodejs 本身的

➢ V8 主线

➢ Libuv 主线程,Libuv 线程池会默认启动4个线程

那为什么 Nodejs 开始时不把 web server 设计成多线程的,以充分利用CPU性能?

个人理解 Ryan Dahl 最初是觉得多线程 http server 的 Blocking I/O 是一种糟糕的设计,想开发一款 Non-Blocking I/O 的 http server,它应该是单线程的,通过异步 I/O 来处理并发请求,这样可以避免多线程 server 的以下问题:

1、 程序的运行不会被 I/O(网络请求、读写磁盘、数据库访问等) 阻塞

2、 死锁

随着2008年12月V8的发布,他认为JavaScript的单线程和事件回调机制(闭包)是实现他的想法的一种可行的方式,于是基于事件驱动的非阻塞 I/O 的 Nodejs 就在2009年发布了初版(他是个天才)。

所以从一开始 Ryan Dahl 就是想要设计单线程非阻塞 I/O 的 http server。

浏览器

显然浏览器是多进程多线程的,但每个页面进程执行 JavaScript 的线程只有一个(在不考虑web worker)的情况下,最终是给到V8执行 Javascript 代码。

总的来看,由于JavaScript语言本身是单线程设计,不管是Nodejs还是浏览器,在不考虑worker threads 或 web workers 的情况下,执行JavaScript的主线程都是单线程的。**那JavaScript 的执行引擎 V8 是单线程的吗?**除了解释和执行JavaScript的主线程,会有垃圾回收的辅助线程。