摘要:对于而言,单线程指的是它的执行线程是单线程。对于来说,单线程不仅不是劣势,它对于降低编程复杂度还有很重要的作用,单线程避免了多线程编程模型多线程死锁状态同步等问题。单线程的应用是脆弱了,但群体的力量是强大的。
我们常听说 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
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中阻塞与非阻塞调用之间的区别,此概述将引用事件循环和libuv,但不需要事先了解这些主题,假设读者对JavaScript语言和Node.js回调模式有基本的了解。 I/O主要指与libuv支持的...
摘要:异步编程在传统编程实践中,大多数操作都是同步发生的。中的异步编程异步是一种输入输出处理的形式,它允许在传输完成之前,其它处理能继续进行。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1759原文:https://blog.risingstack.com/node-hero-async-programming-in-node-js/ ...
摘要:单线程异步非阻塞然后,这又牵扯到了事件循环消息队列,还有微任务宏任务这些。此步的位置不确定某个时刻后,定时器触发线程通知事件触发线程,事件触发线程将回调函数加入消息队列队尾,等待引擎线程执行。 前言 Philip Roberts 在演讲 great talk at JSConf on the event loop 中说:要是用一句话来形容 JavaScript,我可能会这样: Java...
摘要:一异步编程原理显然,上面这种方式和银行取号等待有些类似,只不过银行取号我们并不知道上一个人需要多久才会完成。下面来探讨下中的异步编程原理。 众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着。在事件队列中加一个延时,这样的问题便可以得到缓解...
阅读 3148·2021-11-22 13:54
阅读 3434·2021-11-15 11:37
阅读 3597·2021-10-14 09:43
阅读 3495·2021-09-09 11:52
阅读 3594·2019-08-30 15:53
阅读 2457·2019-08-30 13:50
阅读 2054·2019-08-30 11:07
阅读 884·2019-08-29 16:32