网络工作者的终极指南
在本教程中,我们将介绍web worker,并演示如何使用它们来解决执行速度问题。
内容:
- JavaScript非阻塞I/O事件循环
- 长时间运行的JavaScript函数
- Web Workers
- Browser Worker演示
- 服务器端Web Worker演示
- Node.js Workers的替代方案
- 结论
浏览器和服务器上的JavaScript程序运行在一个处理线程上。这意味着程序一次只能做一件事。简单地说,您的新PC可能有一个32核CPU,但当您的JavaScript应用程序运行时,其中31个CPU处于空闲状态。
JavaScript的单线程避免了复杂的并发情况。如果两个线程同时尝试进行不兼容的更改会发生什么?例如,浏览器可能正在更新DOM,而另一个线程重定向到一个新的URL并从内存中删除该文档。node . js,Deno,和Bun从浏览器继承了相同的单线程引擎。
这不是特定于javascript的限制。大多数语言都是单线程的,但是web选项(如PHP和Python)通常运行在web服务器上,该服务器在每个用户请求的新线程上启动解释器的单独实例。这是资源密集型的,所以Node.js应用程序通常定义自己的web服务器,它运行在单个线程上,异步处理每个传入的请求。
Node.js方法可以更有效地处理更高的流量负载,但长时间运行的JavaScript函数将抵消效率的提高。
在我们演示如何解决web worker的执行速度问题之前,我们将首先研究JavaScript是如何运行的,以及为什么长时间运行的函数是有问题的。
JavaScript非阻塞I/O事件循环
你可能认为一次做一件事会导致性能瓶颈,但JavaScript是异步的,这避免了大多数单线程处理问题,因为:
没有必要等待供用户单击网页上的按钮。
浏览器引发一个事件调用JavaScript函数当点击发生时。
没有必要等待对Ajax请求的响应。
当服务器返回数据时,浏览器引发一个事件,调用JavaScript函数。
Node.js应用程序不需要等待用于数据库查询的结果。
当数据可用时,运行时调用JavaScript函数。
JavaScript引擎运行事件循环.一旦代码的最后一条语句完成执行,运行时将返回并在必要时执行回调之前检查未执行的计时器、挂起的回调和数据连接。
其他操作系统处理线程负责调用输入/输出系统,如HTTP请求、文件处理程序和数据库连接。它们不会阻塞事件循环.它可以继续执行等待队列中的下一个JavaScript函数。
本质上,JavaScript引擎只负责运行JavaScript代码。操作系统处理所有其他I/O操作,这些操作可能导致引擎在发生某些事情时调用JavaScript函数。
长时间运行的JavaScript函数
JavaScript函数通常由事件触发。它们将进行一些处理,输出一些数据,大多数情况下,将在几毫秒内完成,以便事件循环可以继续。
不幸的是,一些长时间运行的函数会阻塞事件循环。假设您正在开发自己的图像处理功能(如锐化、模糊化、灰度化等)。异步代码可以从(或向)文件读取(或写入)数百万字节的像素数据——这对JavaScript引擎几乎没有影响。然而,处理图像的JavaScript代码可能需要几秒钟来计算每个像素。函数阻塞事件循环—并且在它完成之前,没有其他JavaScript代码可以运行.
在浏览器中,用户将无法与页面交互。他们将无法单击、滚动或键入,并且可能会看到“无响应脚本”错误,并选择停止处理。
Node.js服务器应用程序的情况更糟。它不能在函数执行时响应其他请求。如果它需要十秒钟才能完成,每一个此时用户访问将不得不等待长达10秒即使他们没有处理图像.
您可以通过将计算拆分为更小的子任务来解决这个问题。下面的代码使用传入的参数处理不超过1,000个像素(来自数组)<代码>我mageFn函数。然后它用a调用自己<代码>setTimeout延迟1毫秒。事件循环阻塞的时间较短,这样JavaScript引擎就可以在迭代之间处理其他传入事件:
//传递回调函数,图像处理函数,输入图像返回一个输出图像到回调函数函数processImage(回调,imageFn=我= >{},的日记=[]){常量chunkSize=1000;//每次迭代要处理的像素//处理第一个块让imageOut=[],指针=0;processChunk();//处理数据块函数processChunk(){常量pointerEnd=指针+chunkSize;//传递块到图像处理函数imageOut=imageOut.concat(imageFn(的日记.片(指针,pointerEnd)));如果(pointerEnd<的日记.长度){//在短暂延迟后处理下一个块指针=pointerEnd;setTimeout(processChunk,1);}其他的如果(回调){// complete -将输出图像返回回调函数回调(零,imageOut);}}}
这可以防止无响应的脚本,但并不总是实用的。单个执行线程仍然完成所有工作,即使CPU可能有能力做更多的工作。为了解决这个问题,我们可以使用网络工作者。
Web Workers
Web workers允许脚本作为后台线程运行。worker使用独立于主执行线程的引擎实例和事件循环运行。它在不阻塞主事件循环和其他任务的情况下并行执行。
使用工作脚本:
- 主线程发布一条包含所有必要数据的消息。
- worker中的事件处理程序执行并启动计算。
- 完成时,worker将带有返回数据的消息发送回主线程。
- 主线程中的事件处理程序执行、解析传入数据并采取必要的操作。
主线——或任何工人-可以产生任意数量的工人。多个线程可以并行处理单独的数据块,从而比单个后台线程更快地确定结果。也就是说,每个新线程都有启动开销,因此确定最佳平衡可能需要一些实验。
所有的浏览器,Node.js 10+, Deno和Bun都支持类似的语法,尽管服务器运行时可以提供更高级的选项。
Browser Worker演示
下面的演示展示了一个毫秒级的数字时钟,每秒更新60次。同时,你可以启动一个骰子模拟器,投掷任意次数的骰子。默认情况下,它会投掷10个六面骰子1000万次,并记录总数的频率。
在CodeSandbox上查看以上演示:
点击开始扔看时钟;它将在计算运行时暂停。较慢的设备和浏览器可能会抛出“无响应脚本”错误。
现在检查使用web worker复选框,然后再次开始投掷。在计算过程中,时钟继续运行。这个过程可能需要稍长的时间,因为web worker必须启动、接收数据、运行计算并返回结果。随着计算复杂性或迭代的增加,这一点将不那么明显。在某些时候,工作线程应该比主线程快。
专职工作者vs共享工作者
浏览器提供两个worker选项:
与共享工作者通信的每个脚本都传递一个惟一的端口,共享工作者必须使用该端口将数据传递回去。然而,IE或大多数移动浏览器不支持共享工作,这使得它们在典型的web项目中无法使用。
客户端工作人员限制
一个worker独立于主线程和其他worker运行;它不能访问其他线程中的数据,除非该数据显式地传递给worker。一个复制的数据被传递给worker。在内部,JavaScript使用它的结构化克隆算法将数据序列化为字符串。它可以包括本机类型,如字符串、数字、布尔值、数组和对象,但是不函数或DOM节点。
浏览器工作者可以使用控制台、Fetch、XMLHttpRequest、WebSocket和IndexDB等api。它们不能访问文档对象、DOM节点、localStorage和窗口对象的某些部分,因为这可能导致JavaScript用单线程解决的并发冲突问题——比如在重定向的同时更改DOM。
重要提示:worker最适合用于cpu密集型任务。它们对密集的I/O工作没有好处,因为这些工作被卸载给浏览器,并且是异步运行的。
如何使用客户端网络工作者
下面的演示定义了<代码>src/ index.js作为主脚本,在用户单击时启动时钟并启动web worker开始按钮。它定义了工人对象工作脚本的名称为<代码>src/ worker.js(相对于HTML文件):
//在工作线程上运行常量工人=新工人(”。/ src / worker.js”);
一个<代码>onmessage下面是事件处理程序。它在worker将数据发送回主脚本时运行——通常在计算结束时运行。数据可在事件对象的<代码>数据属性,并将其传递给<代码>endDiceRun ()功能:
//从worker接收数据工人.onmessage=函数(e){endDiceRun(e.数据);};
主脚本使用its启动工作<代码>postMessage()方法发送数据(一个名为<代码>cfg):
//向worker发送数据工人.postMessage(cfg);
的<代码>src/ worker.js定义工作代码。进口<代码>src/ dice.js使用importScripts ()
-一个全局工作方法,同步导入一个或多个脚本到工作中。文件引用相对于工作者的位置:
importScripts(”。/ dice.js ');
src / dice.js
定义了一个<代码>d我ceRun ()函数计算投掷统计信息:
//记录投掷骰子的统计信息函数diceRun(运行=1,骰子=2,国=6){常量统计=[];而(运行>0){让总和=0;为(让d=骰子;d>0;d--){总和+ =数学.地板上(数学.随机()*国)+1;}统计[总和]=(统计[总和]||0)+1;运行--;}返回统计;}
注意这是不ES模块(见下文).
src / worker.js
然后定义一个<代码>onmessage()事件处理程序。当主调用脚本(<代码>src/ index.js)向worker发送数据。事件对象具有<代码>.数据属性,该属性提供对消息数据的访问。在这种情况下,它是<代码>cfg对象的属性<代码>.throws,<代码>.dice,<代码>.sides,它们作为参数传递给<代码>d我ceRun ():
onmessage=函数(e){//开始计算常量cfg=e.数据;常量统计=diceRun(cfg.抛出,cfg.骰子,cfg.国);//返回主线程postMessage(统计);};
一个<代码>postMessage()函数将结果发送回主脚本。这调用<代码>worker.onmessage上面所示的处理程序,它运行<代码>endDiceRun ().
总之,线程处理是通过在主脚本和worker之间发送消息来实现的:
- 主脚本定义了一个<代码>工人对象和调用<代码>postMessage()发送数据。
- 工作脚本执行一个<代码>onmessage开始计算的处理程序。
- 工人喊道<代码>postMessage()将数据发送回主脚本。
- 主脚本执行一个<代码>onmessage处理程序来接收结果。
Web工作人员错误处理
除非你使用的是旧的应用程序,现代浏览器中的开发人员工具像任何标准脚本一样支持web worker调试和控制台日志记录。
主脚本可以调用.terminate ()
方法在任何时候结束该worker。如果工作人员未能在特定时间内作出回应,这可能是必要的。例如,如果一个活跃的worker在10秒内没有收到响应,这段代码将终止它:
//主脚本常量工人=新工人(“/ src / worker.js。”);//在10秒后终止worker常量workerTimer=setTimeout(()= >工人.终止(),10000);// worker complete工人.onmessage=函数(e){//取消终止clearTimeout(workerTimer);};// start worker工人.postMessage({somedata:1});
Worker脚本可以使用标准的错误处理技术,比如验证传入数据,试一试
,<代码>抓,<代码>最后,扔
优雅地处理出现的问题,并在需要时向主脚本报告。
你可以在主脚本中使用以下方法检测未处理的worker错误:
onmessageerror
:当worker接收到不能反序列化的数据时触发onerror
:当工作脚本中发生JavaScript错误时触发
返回的事件对象中提供错误详细信息<代码>.filename,<代码>.lineno,<代码>.message属性:
//检测工作人员错误工人.onerror=函数(犯错){控制台.日志(`$ {犯错.文件名}、线$ {犯错.lineno}:$ {犯错.消息}`);}
客户端web worker和ES模块
默认情况下,浏览器web worker是不能够使用ES模块(使用<代码>出口而且<代码>进口语法)。
的<代码>src/ dice.jsFile定义了一个导入到worker中的函数:
importScripts(”。/ dice.js ');
有点不同寻常的是,<代码>src/ dice.js代码也包含在main中<代码>src/ index.js脚本,因此它可以启动与工作进程和非工作进程相同的函数。<代码>src/ index.js加载为ES模块。它不能<代码>进口的<代码>src/ dice.js代码,但它可以加载它作为HTML<代码><>脚本元素,使其在模块中可用:
常量diceScript=文档.createElement(“脚本”);diceScript.src=“/ src / dice.js。”;文档.头.列表末尾(diceScript);
这种情况不太可能发生在大多数应用程序中,除非需要在主脚本和辅助脚本之间共享代码库。
可以通过在workers中添加<代码>{类型:"module"}工作构造函数的参数:
常量工人=新工人(“/ src / worker.js。”,{类型:“模块”});
然后你就可以<代码>出口的<代码>d我ceRun ()函数<代码>src/ dice.js:
出口函数diceRun(运行=1,骰子=2,国=6){/ /……}
然后<代码>进口它在<代码>worker.js模块使用完全限定或相对URL引用:
进口{diceRun}从”。/ dice.js ';
从理论上讲,ES6模块是一个很好的选择,但不幸的是,它们只在基于chromium的浏览器的80版本(2020年发布)中得到支持。您不能在Firefox或Safari中使用它们,这使得它们对于本文所示的示例代码不切实际。
更好的选择是使用捆扎器,例如esbuild或rollup.js.它们可以解析ES模块引用,并将它们打包到单个工作(和主)JavaScript文件中。这简化了编码,并大大提高了worker的速度,因为它们不需要在执行之前解析导入。
客户端服务工作者
服务工作人员是Progressive web Apps用来提供离线功能、后台数据同步和web通知的特殊网络工作者。他们可以:
- 充当浏览器和网络之间的代理,以管理缓存文件
- 在后台运行,即使浏览器或页面没有加载更新数据和接收传入消息
与web worker一样,service worker在单独的处理线程上运行,不能使用DOM之类的api。然而,相似之处也仅此而已:
主线程可以声明service worker的可用性,但两者之间没有任何直接通信。主线程不一定知道service worker正在运行。
Service worker通常不用于cpu密集型计算。它们可以通过缓存文件和进行其他网络优化来间接提高性能。
一个特定的域/路径可以为不同的任务使用许多web worker,但它只能注册一个service worker。
Service worker必须在相同的HTTPS域和路径上,而web worker可以在HTTP上的任何域或路径上操作。
服务工作者超出了本文的范围,但您可以找到更多信息:
服务器端Web Worker演示
Node.js是最常用的服务器JavaScript运行时,它从版本10开始提供worker。
Node.js并不是唯一的服务器运行时:
Deno复制了Web Worker API,因此语法与浏览器代码相同。它还提供了一个兼容模式,如果你想使用运行时的,它可以填充Node.js api工作线程语法.
包子虽然它的目的是同时支持浏览器和Node.js工作api。
您可能正在使用JavaScript无服务器服务,如AWS Lambda、Azure函数、谷歌云函数、Cloudflare workers或Netlify边缘函数等。这些可能会提供类似web worker的api,尽管好处较少,因为每个用户请求都会启动一个单独的隔离实例。
下面的演示展示了一个Node.js进程,它每秒钟将当前时间写入控制台:在新的浏览器选项卡中打开Node.js演示.
然后在主线程上启动掷骰子计算。暂停当前时间输出:
计时器进程12:33:18 PM计时器进程12:33:19 PM计时器进程12:33:20 PM没有线程计算开始…┌─────────┬──────────┐│││值(指数)├─────────┼──────────┤2 2776134│││││5556674 3│││8335819 4│││11110893 5│││13887045 6│││16669114 7│││13885068 8│││11112704 9│││10 8332503│││5556106 11│││2777940 12│└─────────┴──────────┘处理时间:2961 ms没有线程计算完成定时器进程12:33:24点
一旦完成,同样的计算在工作线程上启动。在这种情况下,当骰子处理发生时,时钟继续运行:
工人计算开始…定时器进程12:33:27点定时器进程12:33:28点定时器进程12:33:29点┌─────────┬──────────┐││(指数)值│├─────────┼──────────┤2 2778246│││││3 5556129││4 8335780││││11114930 5│││6 13889458│││7 16659456│││13889139 8│││11111219 9│││10 8331738│││11 5556788│││12 2777117│└─────────┴──────────┘处理时间:2643 ms工人计算完成定时器过程12:33:30点定时器进程12:33:31点定时器进程12:33:32点
工作进程通常比主线程快一点。
如何使用服务器端网络工作者
演示定义了<代码>src/ index.js作为主脚本,其中开始一个<代码>计时器进程(如果它还没有运行),当服务器收到一个新的HTTP请求:
/ /定时器计时器=setInterval(()= >{控制台.日志(`计时器的过程$ {intlTime.格式(新日期())}`);},1000);
的<代码>runWorker ()函数定义了工人对象工作脚本的名称为<代码>src/ worker.js(相对于项目根目录)。它通过一个<代码>workerData变量作为单个值,在这种情况下,是一个具有三个属性的对象:
常量工人=新工人(”。/ src / worker.js”,{workerData:{抛出,骰子,国}});
与浏览器web worker不同,这将启动脚本。没必要跑<代码>工人.postMessage(),尽管您可以使用它来运行<代码>parentPort.在(“信息”)在工作者中定义的事件处理程序。
的<代码>src/ worker.js代码调用<代码>d我ceRun ()与<代码>workerData值并将结果传递回主线程<代码>parentPort.postMessage():
//工作线程进口{workerData,parentPort}从“节点:worker_threads”;进口{diceRun}从”。/ dice.js”;//开始计算常量统计=diceRun(workerData.抛出,workerData.骰子,workerData.国);//发送消息到父脚本parentPort.postMessage(统计);
这就引发了<代码>“消息”主事件<代码>src/ index.js脚本,该脚本接收结果:
//返回结果工人.在(“消息”,结果= >{控制台.表格(结果);});
工作线程在发送消息后终止,这将引发<代码>“退出”事件:
工人.在(“退出”,代码= >{/ /……清理});
你可以根据需要定义其他错误和事件处理程序:
messageerror
:当worker接收到不能反序列化的数据时触发在线
:当工作线程开始执行时触发错误
:当工作脚本中出现JavaScript错误时触发。
内联工作脚本
一个脚本文件可以包含这两个主代码和辅助代码。代码可以检查它是否运行在使用的主线程上<代码>我sMainThread,然后称自己为worker(使用<代码>我mport.meta.url作为ES模块中的文件引用,或者<代码>__filename在CommonJS):
进口{工人,isMainThread,workerData,parentPort}从“节点:worker_threads”;如果(isMainThread){//主线程//创建一个worker常量工人=新工人(进口.元.url,{workerData:{抛出,骰子,国}});工人.在(“消息”,味精= >{});工人.在(“退出”,代码= >{});}其他的{//工作线程常量统计=diceRun(workerData.抛出,workerData.骰子,workerData.国);parentPort.postMessage(统计);}
就我个人而言,我更喜欢分开文件,因为主线程和工作线程可能需要不同的模块。对于简单的单脚本项目,内联工作者可以是一种选择。
服务器端工作人员限制
服务器工作者仍然独立运行,并像在浏览器中一样接收有限的数据副本。
Node.js、Deno和Bun中的服务器端工作线程比浏览器工作线程有更少的API限制,因为没有DOM。当两个或多个worker试图同时向同一个文件写入数据时,可能会出现问题,但在大多数应用程序中不太可能发生这种情况。
您将无法传递和共享复杂的对象,例如数据库连接,因为大多数对象都有无法克隆的方法和函数。然而,你可以做以下其中之一:
异步读取主线程中的数据库数据,并将结果数据传递给worker。
在worker中创建另一个连接对象。这将有启动成本,但如果函数需要进一步的数据库查询作为计算的一部分,这可能是可行的。
重要提示:请记住,worker最适合用于cpu密集型任务。它们对密集的I/O工作没有好处,因为那是卸载给操作系统的,并且是异步运行的。
线程间共享数据
上面所示的主线程和工作线程之间的通信导致了双方的克隆数据。可以在线程之间共享数据SharedArrayBuffer对象,表示固定长度的原始二进制数据。下面的主线程定义了100个从0到99的数字元素,并将它们发送给一个worker:
进口{工人}从“节点:worker_threads”;常量缓冲=新SharedArrayBuffer(One hundred.*Int32Array.BYTES_PER_ELEMENT),价值=新Int32Array(缓冲);价值.forEach((v,我)= >价值[我]=我);常量工人=新工人(”。/ worker.js”);工人.postMessage({价值});
工人可以收到<代码>价值对象:
进口{parentPort}从“节点:worker_threads”;parentPort.在(“消息”,价值= >{价值[0]=One hundred.;});
的元素中,主线程或工作线程都可以更改元素<代码>价值数组,两边都改变了。
这种技术可以提高效率,因为不需要在两个线程中序列化数据。也有缺点:
- 只能共享整数。
- 仍然有必要发送一条消息来表明数据已经更改。
- 存在两个线程同时更改相同值并失去同步的风险。
也就是说,这一过程有利于那些需要处理大量图像或其他数据的高性能游戏。
Node.js Workers的替代方案
并不是每个Node.js应用程序都需要或可以使用worker。一个简单的web服务器应用程序可能没有复杂的计算。它继续在单个处理线程上运行,并且随着活动用户数量的增加,它的响应将变得不那么灵敏。设备可能具有相当大的处理能力,多个CPU核心仍未使用。
下面几节描述通用多线程选项。
Node.js子进程
Node.js在worker和Deno和Bun之前支持子进程。
本质上,它们可以启动另一个应用程序(不一定用JavaScript)、传递数据并接收结果。他们的工作方式与工人类似,但通常效率较低,流程更密集。
当您运行复杂的JavaScript函数(可能在同一个项目中)时,最好使用worker。当您启动另一个应用程序(如Linux或Python命令)时,子进程是必要的。
node . js集群
node . js集群允许您派生任意数量的相同进程,以更有效地处理负载。初始的主进程可以fork自己——可能对于返回的每个CPU一次os.cpus ()
.它还可以处理实例失败时的重启,并代理fork进程之间的通信消息。
的<代码>集群标准库提供的属性和方法包括:
.isPrimary
:返回<代码>真正的对于主要的主进程(较旧的<代码>.isMaster也支持).fork ()
:生成child worker进程.isWorker
:返回<代码>真正的对于工作进程
这个例子为设备上可用的每个CPU/核心启动一个web服务器工作进程。一台4核机器将生成4个web服务器实例,因此它可以处理多达4倍的处理负载。它还会重新启动任何失败的进程,以使应用程序更加健壮:
/ / app.js进口集群从节点:集群的;进口过程从节点:过程的;进口{cpu}从节点:操作系统的;进口http从节点:http的;常量cpu=cpu().长度;如果(集群.isPrimary){控制台.日志(`启动主进程:$ {过程.pid}`);// fork worker为(让我=0;我<cpu;我++){集群.叉();}// worker失败事件集群.在(“退出”,(工人,代码,信号)= >{控制台.日志(`工人$ {工人.过程.pid}失败的`);集群.叉();});}其他的{//启动HTTP服务器http.createServer((要求的事情,res)= >{res.writeHead(200);res.结束(“你好!”);}).听(8080);控制台.日志(`已启动工作进程:$ {过程.pid}`);}
所有进程共享端口8080,任何进程都可以处理传入的HTTP请求。运行应用程序时的日志显示如下内容:
启动主进程:1001已启动工作进程:1002已启动工作进程:1003已启动工作进程:1004已启动工作进程:1005..等...工人1002failed启动worker进程:1006
很少有Node.js开发人员尝试集群。上面的示例很简单,而且效果很好,但是当您试图处理消息、失败和重新启动时,代码可能会变得越来越复杂。
流程管理人员
Node.js进程管理器可以帮助运行一个Node.js应用程序的多个实例,而无需手动编写集群代码。最著名的是PM2.下面的命令为每个CPU/核启动应用程序的实例,并在它们失败时重新启动任何实例:
Pm2 start ./app.js -i Max .js
应用程序实例在后台启动,所以它非常适合在活动服务器上使用。您可以通过输入来检查哪些进程正在运行<代码>pm2状态(节录输出):
pm2美元地位┌────┬──────┬───────────┬─────────┬─────────┬──────┬────────┐│id│name│命名空间│版本│模式│pid│正常运行时间│├────┼──────┼───────────┼─────────┼─────────┼──────┼────────┤│1│app│default1.0.0│集群│1001│4d││2│app│default1.0.0│集群│10024 d││└────┴──────┴───────────┴─────────┴─────────┴──────┴────────┘
PM2还可以运行用Deno、Bun、Python或任何其他语言编写的非node .js应用程序。
容器管理
集群和进程管理器将应用程序绑定到特定的设备。如果您的服务器或操作系统依赖项失败,无论运行的实例数量有多少,应用程序都将失败。
容器的概念与虚拟机类似,不同之处在于,容器模拟的不是完整的硬件设备,而是操作系统。容器是围绕单个应用程序的轻量级包装,其中包含所有必要的操作系统、库和可执行文件。它提供了Node.js(或任何其他运行时)和应用程序的隔离实例。单个设备可以运行多个容器,因此不需要集群或进程管理。
容器超出了本文的范围,但是众所周知的解决方案包括码头工人而且Kubernetes.他们可以在分布传入流量的同时,在任意数量的设备上启动和监视任意数量的容器,甚至在不同的位置。
结论
JavaScript worker可以通过在并行线程中运行cpu密集型计算来提高客户机和服务器上的应用程序性能。服务器端工作者还可以在单独的线程中运行更危险的函数,并在处理时间超过一定限制时终止它们,从而使应用程序更加健壮。
在JavaScript中使用worker很简单,但是:
工作人员不能访问所有api,比如浏览器DOM。它们最适合用于长时间运行的计算任务。
对于密集但异步的I/O任务(如HTTP请求和数据库查询),worker不太需要。
启动一个worker是有开销的,所以可能需要一些实验来确保它们提高性能。
进程和容器管理等选项可能比服务器端多线程更好。
也就是说,当你遇到绩效问题时,员工是一个有用的考虑工具。