前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[译]理解 Node.js 的中 Worker Threads

[译]理解 Node.js 的中 Worker Threads

作者头像
腾讯IVWEB团队
发布于 2020-06-28 02:48:50
发布于 2020-06-28 02:48:50
2.1K00
代码可运行
举报
运行总次数:0
代码可运行

原文:https://nodesource.com/blog/worker-threads-nodejs

理解 Node 的底层对于理解 Workers 是很有必要的。

当一个 Node.js 的应用启动的同时,它会启动如下模块:

  • 一个进程
  • 一个线程
  • 事件循环机制
  • JS 引擎实例
  • Node.js 实例

一个进程:process 对象是一个全局变量,可在 Node.js 程序中任意地方访问,并提供当前进程的相关信息。

一个线程:单线程意味着在当前进程中同一时刻只有一个指令在执行。

事件循环:这是 Node.js 中需要重点理解的一个部分,尽管 JavaScript 是单线程的,但通过使用回调,promises, async/await 等语法,基于事件循环将对操作系统的操作异步化,使得 Node 拥有异步非阻塞 IO 的特性。

一个 JS 引擎实例:即一个可以运行 JavaScript 代码的程序。

一个 Node.js 实例:即一个可以运行 Node.js 环境的程序。

换言之,Node 运行在单线程上,并且在事件循环中同一时刻只有一个进程的任务被执行,每次同一时刻只会执行一段代码(多段代码不会同时执行)。这是非常有效的,因为这样的机制足够简单,让你在使用 JavaScript 的时候无需担心并发编程的问题。

这样的原因在于 JavaScript 起初是用于客户端的交互(比如 web 页面的交互或表单的验证),这些逻辑并不需要多线程这样的机制来处理。

所以这也带来了另一个缺点:如果你需要使用 CPU 密集型的任务,比如在内存中使用一个大的数据集进行复杂计算,它会阻塞掉其他进程的任务。同样的,当你在发起一个有 CPU 密集型任务的远程接口请求时,也同样会阻塞掉其他需要被执行的请求。

如果一个函数阻塞了事件循环机制直到这个函数执行完才能执行下一个函数,那么它就被认为是一个阻塞型函数。一个非阻塞的函数是不会阻塞住事件循环进行下一个函数的执行的,它会使用回调通知事件循环函数任务已执行完毕。

最佳实践:不要阻塞事件循环,要让事件循环保持不断运行,并且注意避免使用回阻塞线程的操作比如同步的网络接口调用或死循环。

区分开 CPU 密集型操作与 I/O(input/output) 密集型操作是很重要的。像前面所说的,Node.js 并不会同时执行多段代码,只有 I/O 操作才会同时去执行,因为它们是异步的。

所以 Worker Threads 对于 I/O 密集型操作是没有太大的帮助的,因为异步的 I/O 操作比 worker 更有效率,Wokers 的主要作用是用于提升对于 CPU 密集型操作的性能。

其他方案

此外,目前已经存在很多对于 CPU 密集型操作的解决方案,比如多进程(cluster API)方案,保证了充分利用多核 CPU。

这个方案的好处在于进程之间是相互独立的,如果一个进程出现了问题,并不会影响到其他进程。此外它们还拥有稳定的 API,然而,这也意味着不能同享内存空间,而且进程间通信只能通过 JSON 格式的数据进行交互。

JavaScript 和 Node.js 不会有多线程,理由如下:

所以,人们可能会认为添加一个创建和同步线程的 Node.js 核心模块就可以解决 CPU 密集型操作的需求。

然而并不是,如果添加多线程模块,将会改变语言本身的特性。添加多线程模块作为可用的类或者函数是不可能的。在一些支持多线程的语言比如 Java 中,使用同步特性来使得多个线程之间的同步能够实现。

并且一些数字类型是不够原子性的,这意味着如果你不同步操作它们,在多线程的同时执行计算的情况下,变量的值可能会不断变动,没有确定的值,变量的值可能经过一个线程计算后改变了几个字节,在另一个线程计算后有改变了其他几个字节的数据。比如,在 JavaScript 中一些简单的计算像 0.1 + 0.2 的结果中小数部分有 17 位(小数的最高位数)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var x = 0.1 + 0.2; // x will be 0.30000000000000004

但是浮点数的计算并不是 100% 精准的。所以如果不同步计算,小数部分的数字就会因为多个线程永远没有一个准确的数字。

最佳实践

所以解决 CPU 密集型操作的性能问题是使用 Worker Threads。浏览器在很久之前就已经有了 Workers 特性了。

单线程下的 Node.js:

  • 一个进程
  • 一个线程
  • 一个事件循环
  • 一个 JS 引擎实例
  • 一个 Node.js 实例

多线程 Workers 下 Node.js 拥有:

  • 一个进程
  • 多个线程
  • 每个线程都拥有独立的事件循环
  • 每个线程都拥有一个 JS 引擎实例
  • 每个线程都拥有一个 Node.js 实例

就像下图:

Worker_threads 模块允许使用多个线程来同时执行 JavaScript 代码。使用下面这个方式引入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const worker = require('worker_threads');

Worker Threads 已经被添加到 Node.js 10 版本中,但是仍处于实验阶段。

使用 Worker threads 我们可以在在同一个进程内可以拥有多个 Node.js 实例,并且线程可以不需要跟随父进程的终止的时候才被终止,它可以在任意时刻被终止。当 Worker 线程销毁的时候分配给该 Worker 线程的资源依然没有被释放是一个很不好的操作,这会导致内存泄漏问题,我们也不希望这样。我们希望这些分配资源能够嵌入到 Node.js 中,让 Node.js 有创建线程的能力,并且在线程中创建一个新的 Node.js 实例,本质上就像是在同一个进程中运行多个独立的线程。

Worker Threads 有如下特性:

  • ArrayBuffers 可以将内存中的变量从一个线程转到另外一个
  • SharedArrayBuffer 可以在多个线程中共享内存中的变量,但是限制为二进制格式的数据。
  • 可用的原子操作,可以让你更有效率地同时执行某些操作并且实现竞态变量
  • 消息端口,用于多个线程间通信。可以用于多个线程间传输结构化的数据,内存空间
  • 消息通道就像多线程间的一个异步的双向通信通道。
  • WorkerData 是用于传输启动数据。在多个线程间使用 postMessgae 进行传输的时候,数据会被克隆,并将克隆的数据传输到线程的 contructor 中。

API:

  • const { worker, parantPort } = require('worker_threads'); =>worker 函数相当于一个独立的 JavaScript 运行环境线程,parentPort 是消息端口的一个实例
  • new Worker(filename) or new Worker(code, { eval: true }) =>启动 worker 的时候有两种方式,可以通过传输文件路径或者代码,在生产环境中推荐使用文件路径的方式。
  • worker.on('message'),worker.postMessage(data) => 这是多线程间监听事件与推送数据的方式。
  • parentPort.on('message'), parentPort.postMessage(data) => 在线程中使用 parentPort.postMessage 方式推送的数据可以在父进程中使用 worker.on('message') 的方式接收到,在父进程中使用 worker.postMessage() 的方式推送的数据可以在线程中使用 parentPort.on('message') 的方式监听到。

例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const { Worker } = require('worker_threads');

const worker = new Worker(`
const { parentPort } = require('worker_threads');
parentPort.once('message',
    message => parentPort.postMessage({ pong: message }));  
`, { eval: true });
worker.on('message', message => console.log(message));      
worker.postMessage('ping');
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ node --experimental-worker test.js
{ pong: ‘ping’ }

上面例子所做的也就是使用 new Worker 创建一个线程,线程中的代码监听了 parentPort 的消息,并且当接收到数据的时候只触发一次回调,将收到的数据传输回父进程中。

你需要使用 --experimental-worker 启动程序因为 Workers 还在实验阶段。

另一个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const {
	Worker, isMainThread, parentPort, workerData
} = require('worker_threads');

if (isMainThread) {
    module.exports = function parseJSAsync(script) {
        return new Promise((resolve, reject) => {
        	const worker = new Worker(filename, {
        		workerData: script
    		});
            worker.on('message', resolve);
            worker.on('error', reject);
            worker.on('exit', (code) => {
                if (code !== 0)
                    reject(new Error(`Worker stopped with exit code ${code}`));
            });
         });
    };
} else {
    const { parse } = require('some-js-parsing-library');
    const script = workerData;
    parentPort.postMessage(parse(script));
}

上面代码中:

  • Worker: 相当于一个独立的 JavaScirpt 运行线程。
  • isMainThread: 如果为 true 的话说明代码不是运行在 Worker 线程中
  • parentPort: 消息端口被使用来进行线程间通信
  • workerData:被传入 worker 的 contructor 的克隆数据。

在实际使用中,应该使用线程池的方式,不然不断地创建 worker 线程的代价将会超过它带来的好处。

对于 Worker 的使用建议:

  • 传输原生的句柄比如 sockets,http 请求
  • 死锁检测。死锁是一种多个进程间被阻塞的情况,原因是每一个进程都持有一部分资源并等待另一个进程释放它所持有的资源。在 Workers Threads 中死锁检测是非常有用的特性
  • 更好的隔离,所以如果一个线程中受影响,它不会影响到其他线程。

对于 Worker 的一些不好的想法:

  • 不要认为 Workers 会带来不可思议的速度提升,有时候使用线程池会是更好的选择。
  • 不要使用 Workers 来并行执行 I/O 操作。
  • 不要认为创建 Worker 进程的开销是很低的。

最后

Chrome devTools 支持 Node.js 中的 Workers 线程特性。worker_threads 是一个实验模块,如果你需要在 Node.js 中运行 CPU 密集型的操作,目前不建议在生产环境中使用 worker 线程,可以使用进程池的方式来代替。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Typecho修改gravatar头像源为国内服务器源
Typecho的评论默认使用的是Gravata头像,每次页面打开总是卡在gravatar.com的链接,虽然匹配了QQ邮箱调用QQ头像的功能,但是对于非QQ邮箱调用的依然是Gravatar头像,就对头像源进行了替换,这里分享一个关于Gravata头像源CDN服务器,速度确实不错。
回忆大大
2021/08/16
2.8K0
Typecho修改Gravata头像源
Typecho默认使用的是Gravata头像,Gravatar大多数的链接在国内都被墙了,加载失败不说,还影响博客加载速度。因为大多数博客主题可以直接在后台修改头像原地址,也只是影响到了评论头像输出,我这里刚刚修改了后台模板,刚登上后台卡的一批头像还加载不出来,头像源应该使用的还是Typecho默认的头像源,这里记录一下通过修改程序源码来替换Gravatar头像地址为国内比较快的头像源,达到更好的体验。
qiangzai
2021/12/21
3.4K1
Typecho修改Gravata头像源
修改Typecho评论调用QQ头像
Typecho默认头像来自Gravatar,没设置过头像就丑丑的,想替换为QQ头像。之前用的WordPress,对Typecho不是很熟悉,于是进行百度。 Typecho似乎百度相关资料特别少,唯一能找到的是巷子工坊的两篇文章,但他实现的效果似乎仅支持QQ邮箱,普通邮箱反而不适用。 经过反复测试,最终解决办法如下:
半夜喝可乐
2024/09/30
1220
typecho使用镜像源对头像进行加速
Typecho的评论使用了Gravata头像,由于国内那堵墙导致访问速度缓慢,每次页面打开头像是加载最慢的,而且gravatar目前服务极不稳定,推荐用国内的源替换,虽然可以对QQ邮箱进行兼容,但是终究有意外,于是对头像源进行了替换是最好的选择,头像源最好用的也就v2ex之类的。
乔千
2020/04/16
1.1K0
Typecho 更换Gravatar国内源,解决Gravatar头像无法加载
Gravatar是Globally Recognized Avatar的缩写,意为“全球通用头像”,如果在Gravatar的服务器上放置了你自己的头像,只要提供你与这个头像关联的Email地址,就能够显示出你的Gravatar头像来
Xcnte
2021/12/14
2.6K0
Gravatar头像被墙问题彻底解决
主题里面 functions.php 文件加入如下代码,从代码可以看出,它的作用是替换gravatar头像获取地址中的域名。比如改成国内的代理。 注意代码中需要替换的地址,如果发现加了函数还是没变化,请先看看头像图片指向地址是否被替换了。 因为现在有的模板用了cn.gravatar.com之类的新域名,遗憾的是也一样被墙了。所以替换的时候,要看看头像当前使用的域名。
sean.liu
2022/09/07
6070
彻底对令人头疼的Gravatar头像说再见,正式换上国内的Cravatar头像源
这意味着我必须得去访问Gravatar的官网,我自用的节点又死了,导致开个Gravatar官网都很卡
imzql
2021/12/28
10.6K1
彻底对令人头疼的Gravatar头像说再见,正式换上国内的Cravatar头像源
解决Typecho系统Gravatar头像无法加载的问题
Gravatar的全称是Globally Recognized Avatar,指的是“全球通用头像”。在Gravatar的服务器上设置了你自己的头像,那么在任何支持Gravatar的博客或者留言本上评论时,只要提供你与这个头像关联的邮箱地址,就可以展示你在Gravatar上设置的头像来。
浩瀚博客
2022/04/11
1K0
Gravatar头像被墙的解决办法
在config.inc.php文件中添加以下其中之一即可 /** 更换头像源 */ define('__TYPECHO_GRAVATAR_PREFIX__', 'https://cdn.v2ex.com/gravatar/'); /** 更换头像源 */ define('__TYPECHO_GRAVATAR_PREFIX__', 'https://gravatar.loli.net/avatar/'); 更多镜像源(直接替换上边的地址就行了) http://gravatar.ihuan.me/avata
团团生活志
2022/08/16
3000
Typecho——非插件方式实现QQ和Gravatar缓存到本地
体验: https://oyo.cool/ | https://wangyangyang.vip/ | https://easybe.org/ [亚太地区的服务器暂时关闭了,cnmae转发不行会被拦截]
思索
2024/08/15
1160
Typecho——非插件方式实现QQ和Gravatar缓存到本地
全球通用头像Gravatar配置和镜像地址搜集
很多博客的评论,都能看到一些个性化的头像。这些头像和博客/网站有点关系,需要博客/网站配置支持Gravata,但关系又不大,因为它是个人的邮箱头像,每个人可以自由设定自己的邮箱头像。
六月河
2022/08/23
1.3K0
typecho 更换 gravatar 头像源
其中 https://sdn.geekzu.org/avatar/ 是一个国内源链接,这个是我目前找到的比较稳定的源。
子舒
2022/06/09
1.7K0
typecho 更换 gravatar 头像源
全球通用头像Gravatar配置和镜像地址搜集
很多博客的评论,都能看到一些个性化的头像。这些头像和博客/网站有点关系,需要博客/网站配置支持Gravata,但关系又不大,因为它是个人的邮箱头像,每个人可以自由设定自己的邮箱头像。
六月河
2022/09/06
2.9K0
解决七牛云存储缓存加速Gravatar 头像图片路径url 参数失效的问题
前天分享了《通过七牛云存储 缓存加速Gravatar头像,解决被墙问题》,不过这昨天发现通过七牛云存储缓存加速Gravatar 头像,会导致头像图片所在的路径 url 参数失效,通过参数来定义图片宽高大小没戏了。这样七牛加载的头像图片一律是默认的80x80大小,虽然在CSS 定义下看起来没有区别,但无缘无故加载多了1kb 左右的体积实在不爽。苛刻的Jeff 决定解决这个问题。 问题呈现 如果你熟悉get_avatar函数的使用,你可能明白下面我所说的内容。不熟悉的也没关系,可以先阅读一下WordPress
Jeff
2018/01/19
1.2K0
解决七牛云存储缓存加速Gravatar 头像图片路径url 参数失效的问题
Cravatar - 互联网公共头像服务
Cravatar 当前由 LitePress 提供维护支持,LitePress 诞生的目的是为WordPress 在中国搭建起稳定运行所需的所有基础设施,并使其完全本土化。
Breeze.
2022/04/11
9430
Cravatar - 互联网公共头像服务
国内Gravatar头像的完美替代方案Cravatar
国内一直无法正常加载Gravatar,严重拖慢 Typecho/Wordpress 的加载速度,另外一些新手可能也不知道如何申请头像。 Cravatar头像申请地址 进入Cravatar头像网站,用自己常用的邮箱注册,登录后点击“立即创建你的头像”。
TOMD
2022/08/10
1.6K0
国内Gravatar头像的完美替代方案Cravatar
Akina For Typecho主题修改记录分享
大多数博主,基本都换过各种类型的博客程序,WordPress、Typecho、hexo、Z-Blog等等太多了,最后选择Typecho,就是因为小巧不臃肿。本人一共使用过两款:Cactus来自仙岛驿站和Akina来自子虚之人。
目的地-Destination
2023/10/12
3700
Akina For Typecho主题修改记录分享
【网站优化经验】Wordpress的代码与功能简单优化
现在,我们就要针对wordpress影响网站响应速度的因素进行具体问题具体分析,并探求正确的方法论高效率有效地解决问题。
幻影龙王
2021/09/11
1.2K0
【网站优化经验】Wordpress的代码与功能简单优化
Cravatar替代Gravatar
之前一直用的loli的gravatar镜像服务,可能是自己写的function文件中的代码有问题,有时候头像加载不出来。今天又搜索了一下发现了cravatar服务:
obaby
2023/02/22
5830
利用腾讯云CDN反代Gravatar镜像实现头像加速
很多博客在安装来wp、typecho等博客程序,由于Gravarar头像在国内访问很慢,网上很多教程都是利用Nginx进行反代,其实利用七牛、阿里、腾讯CDN也可以反代。
幻影龙王
2021/09/12
6K0
利用腾讯云CDN反代Gravatar镜像实现头像加速
推荐阅读
相关推荐
Typecho修改gravatar头像源为国内服务器源
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验