资讯专栏INFORMATION COLUMN

JavaScript 单线程不简单.md

Lowky / 2634人阅读

摘要:对于而言,单线程指的是它的执行线程是单线程。对于来说,单线程不仅不是劣势,它对于降低编程复杂度还有很重要的作用,单线程避免了多线程编程模型多线程死锁状态同步等问题。单线程的应用是脆弱了,但群体的力量是强大的。

我们常听说 JavaScript 是单线程的,那这个单线程是什么意思呢?单线程是否意味 JavaScript 存在性能缺陷呢?

在浏览器端,JavaScript 单线程指的是 JavaScript 的执行线程与 UI 渲染线程共用一个线程。对于 NodeJS 而言,单线程指的是它的 JavaScript 执行线程是单线程。虽然 JavaScript 只能单线程执行,但 JavaScript 引擎可不是,它能够创建多个线程为主线程服务。Web Worker 已经得到大部分浏览器的支持,NodeJS 也拥有自己的线程池来处理 I/O 操作。无论前端还是后端,JavaScript 已经能够利用多个线程来提升程序性能了。

NodeJS 单线程异步 I/O 模型

NodeJS 是单线程异步 I/O 模型。换句话说,NodeJS 代码执行占用一个线程,而代码中的 I/O 操作则是交给其它线程执行,执行完毕后将结果交还给主线程。

对于 NodeJS 来说,单线程不仅不是劣势,它对于降低编程复杂度还有很重要的作用,单线程避免了多线程编程模型多线程死锁、状态同步等问题。而异步 I/O 避免了单线程同步编程模型的阻塞问题,使 CPU 得到更充分的使用。

NodeJS 异步 I/O 模型的实现离不开 libuv 层,libuv 提供了一个线程池来执行 I/O 操作,执行完毕后再将结果返回给执行线程,因此 I/O 操作不会阻塞执行线程地继续执行。libuv 是一个事件驱动的异步 I/O 库,它是跨平台的,在 *nix 平台下,自行实现了线程池,在 windows 平台采用了 IOCP,IOCP 内部仍是线程池原理,libuv 的线程池默认为 4 个线程。接下来我们在 Linux 环境下看一看 NodeJS 的多个线程。

查看 NodeJS 多线程

首先,我们需要先编写一个 js 脚本,写入一个定时器使得脚本不会因为执行完毕而被关掉。

setInterval(function () {}, 1000)

node命令执行该脚本,开启另一个窗口(或者把程序放后台执行)来查看 NodeJS 进程下的线程情况。

$ ps -a
  PID TTY          TIME CMD
16699 pts/2    00:00:00 node
16706 pts/0    00:00:00 ps
$ ps -L -p 16699
  PID   LWP TTY          TIME CMD
16699 16699 pts/2    00:00:00 node
16699 16700 pts/2    00:00:00 V8 WorkerThread
16699 16701 pts/2    00:00:00 V8 WorkerThread
16699 16702 pts/2    00:00:00 V8 WorkerThread
16699 16703 pts/2    00:00:00 V8 WorkerThread
16699 16704 pts/2    00:00:00 node

可以看到包括 V8 引擎的工作线程在内,已经开启了 6 个线程(MAC OS 系统用ps -M -p 命令)。当前,线程池还未被创建,只有进行 I/O 操作后,线程池才会被创建。在脚本中添加异步读取文件的代码来激活线程池。

require("fs").readFile("test.js", function () {})
setInterval(function () {}, 1000)

重新启动脚本,可以看到,启动的 4 个新线程正是 libuv 线程池默认的 4 个线程。

$ ps -a
  PID TTY          TIME CMD
16745 pts/2    00:00:00 node
16755 pts/0    00:00:00 ps
$ ps -L -p 16745
  PID   LWP TTY          TIME CMD
16745 16745 pts/2    00:00:00 node
16745 16746 pts/2    00:00:00 V8 WorkerThread
16745 16747 pts/2    00:00:00 V8 WorkerThread
16745 16748 pts/2    00:00:00 V8 WorkerThread
16745 16749 pts/2    00:00:00 V8 WorkerThread
16745 16750 pts/2    00:00:00 node
16745 16751 pts/2    00:00:00 node
16745 16752 pts/2    00:00:00 node
16745 16753 pts/2    00:00:00 node
16745 16754 pts/2    00:00:00 node

可以通过修改环境变量process.env.UV_THREADPOOL_SIZE(最大 128)使 NodeJS 支持更多地线程。

// js
process.env.UV_THREADPOOL_SIZE = 64
require("fs").readFile("test.js", function () {})
setInterval(function () {}, 1000)

// bash
$ ps -a
  PID TTY          TIME CMD
16782 pts/2    00:00:00 node
16852 pts/0    00:00:00 ps
$ ps -L -p 16782 | wc -l
71

重新执行脚本,可以看到减去第一行和 6 个初始线程,有 64 个线程在为 NodeJS 的异步 I/O 服务。

高并发和高可用

JavaScript 是单线程,但 JavaScript 引擎能够创建多个线程来服务与主线程,而 NodeJS 的主线程就像一个调度员,它能够将 I/O 操作,例如网络请求,分发给其它线程进行处理,在通过事件机制将结果返回给主线程,因此,NodeJS 编写的服务器能够支持极大的并发量,这也是 NodeJS 的优势所在。NodeJS 主线程不宜进行大量地计算,因为这会阻塞主线程的运行。所以一般来说,NodeJS 适合 I/O 密集型场景,不适合 CPU 密集型场景。

除了多线程的支持,NodeJS 还提供 child_process 和 cluster 接口允许用户创建很多子进程来处理任务。单线程的 NodeJS 应用是脆弱了,但群体的力量是强大的。多进程、多线程的 NodeJS 才是服务器性能和稳定性的保证。

参考资料

http://docs.libuv.org/en/latest/threadpool.html

《深入浅出 NodeJS》

https://nodejs.org/dist/latest-v8.x/docs/api/child_process.html

https://nodejs.org/dist/latest-v8.x/docs/api/cluster.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85022.html

相关文章

  • Node.js 指南(阻塞与非阻塞概述)

    摘要:标准库中的所有方法都提供非阻塞的异步版本,并接受回调函数,某些方法还具有对应的阻塞方法,其名称以结尾。比较代码阻塞方法同步执行,非阻塞方法异步执行。 阻塞与非阻塞概述 此概述介绍了Node.js中阻塞与非阻塞调用之间的区别,此概述将引用事件循环和libuv,但不需要事先了解这些主题,假设读者对JavaScript语言和Node.js回调模式有基本的了解。 I/O主要指与libuv支持的...

    zebrayoung 评论0 收藏0
  • 【Node Hero】3. 理解异步编程

    摘要:异步编程在传统编程实践中,大多数操作都是同步发生的。中的异步编程异步是一种输入输出处理的形式,它允许在传输完成之前,其它处理能继续进行。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1759原文:https://blog.risingstack.com/node-hero-async-programming-in-node-js/ ...

    kevin 评论0 收藏0
  • 总结:JavaScript异步、事件循环与消息队列、微任务与宏任务

    摘要:单线程异步非阻塞然后,这又牵扯到了事件循环消息队列,还有微任务宏任务这些。此步的位置不确定某个时刻后,定时器触发线程通知事件触发线程,事件触发线程将回调函数加入消息队列队尾,等待引擎线程执行。 前言 Philip Roberts 在演讲 great talk at JSConf on the event loop 中说:要是用一句话来形容 JavaScript,我可能会这样: Java...

    qianfeng 评论0 收藏0
  • JavaScript异步编程原理

    摘要:一异步编程原理显然,上面这种方式和银行取号等待有些类似,只不过银行取号我们并不知道上一个人需要多久才会完成。下面来探讨下中的异步编程原理。 众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着。在事件队列中加一个延时,这样的问题便可以得到缓解...

    lidashuang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<