Node가 CPU 집약적인 작업을 처리하는 방법은 무엇인가요? 다음 문서에서는 Node가 CPU 집약적인 작업을 처리하는 방법을 보여줍니다. 이것이 도움이 되기를 바랍니다.
우리는 일상 업무에서 다음과 같은 말을 어느 정도 들어왔습니다.
노드는
비 차단 I/O
(비 차단 I/O)이고입니다. event 이벤트 중심의 <code>JavaScript 런타임 환경
(런타임)이므로 웹 서비스와 같이 I/O 집약적인 애플리케이션을 구축하는 데 매우 적합합니다.非阻塞I/O
(non-blocking I/O)和事件驱动
(event-driven)的JavaScript运行环境
(runtime),所以它非常适合用来构建I/O密集型应用,例如Web服务等。
不知道当你听到类似的话时会不会有和我一样的疑惑:单线程的Node为什么适合用来开发I/O密集型应用?
按道理来说不是那些支持多线程的语言(例如Java和Golang)做这些工作更加有优势吗?
要搞明白上面的问题,我们需要知道Node的单线程指的是什么。【相关教程推荐:nodejs视频教程】
其实我们说Node是单线程的,说的只是我们的JavaScript代码
是在同一个线程(我们可以叫它主线程
)里面运行的,而不是说Node只有一个线程在工作
。实际上Node底层会使用libuv的多线程能力
将一部分工作(基本都是I/O相关操作)放在一些主线程之外
的线程里面执行,当这些任务完成后再以回调函数
的方式将结果返回到主线程的JavaScript执行环境。可以看看示意图:
注: 上图是Node事件循环
(Event Loop)的简化版,实际上完整的事件循环会有更多的阶段例如timers等。
从上面的分析中我们知道Node会将所有的I/O操作通过libuv的多线程能力分散到不同的线程里面执行,其余的操作都放在主线程里面执行。那么为什么这种做法就比Java或者Golang等其它语言更适合做I/O密集型应用呢?我们以开发Web服务为例,Java和Golang等主流后端编程语言的并发模型是基于线程
(Thread-Based)的,这也就意味他们对于每一个网络请求都会创建一个单独的线程
来处理。可是对于Web应用来说,主要还是对数据库的增删改查,或者请求其它外部服务等网络I/O操作
,而这些操作最后都是交给操作系统的系统调用来处理的(无需应用线程参与),并且十分缓慢(相对于CPU时钟周期来说)
,因此被创建出来的线程大多数时间是无事可做
的而且我们的服务还要承担额外的线程切换
开销。和这些语言不一样的是Node没有为每个请求都创建一个线程,所有请求的处理
都发生在主线程中,因此没有了线程切换
的开销,并且它还会通过线程池
的形式异步处理这些I/O
操作,然后通过事件的形式告诉主线程结果从而避免阻塞主线程的执行,因此它理论上
是更高效的。这里值得注意的是我只是说Node理论上
是更快的,实际上真不一定。这是因为现实中一个服务的性能会受到很多方面的影响,我们这里只是考虑了并发模型
这一个因素,而其它因素例如运行时消耗也会影响到服务的性能,举个例子,JavaScript
是动态语言,数据的类型需要在运行时进行推断,而Golang
和Java
都是静态语言它们的数据类型在编译时就可以确定,所以它们实际执行起来可能会更快,占用内存也会更少。
上面我们提到Node除了I/O相关的操作其余操作都会在主线程里面执行,所以当Node要处理一些CPU密集型
단일 스레드 노드가 I/O 집약적인 애플리케이션 개발에 왜 적합한가요?
논리적으로 말하면 멀티스레딩을 지원하는 언어(예: Java 및 Golang)가 이러한 작업을 수행하는 데 더 유리하지 않을까요? 🎜🎜위 문제를 이해하려면 Node의 단일 스레드가 무엇을 의미하는지 알아야 합니다. [관련 튜토리얼 권장사항: 🎜nodejs 동영상 튜토리얼🎜]🎜JavaScript 코드
가 동일한 스레드(메인 스레드
라고 부를 수 있음)에서 실행되고 있다는 것입니다. 스레드가 작동 중입니다. 실제로 Node의 최하위 계층은 libuv의 멀티스레딩 기능
을 사용하여 메인 스레드 외부
일부 스레드에서 작업의 일부(기본적으로 I/O 관련 작업)를 실행합니다. 이러한 작업이 완료되면 결과가 콜백 함수
형식으로 메인 스레드의 JavaScript 실행 환경에 반환됩니다. 회로도를 살펴보세요: 🎜🎜🎜🎜참고: 위 그림은 노드 이벤트 루프
(이벤트 루프)의 단순화된 버전입니다. 실제로 완전한 이벤트 루프에는 타이머 등과 같은 더 많은 단계가 있습니다. . 🎜Java, Golang과 같은 주류 백엔드 프로그래밍 언어의 동시성 모델은 스레드 기반
(스레드 기반)입니다. 처리할 모든 네트워크 요청에 대해 별도의 스레드
를 만듭니다. 그러나 웹 애플리케이션의 경우 주요 작업에는 데이터베이스 추가, 삭제, 수정 및 쿼리, 기타 외부 서비스 및 기타 네트워크 I/O 작업 요청
이 포함되며 이러한 작업은 궁극적으로 운영 체제를 처리하기 위해 호출되며(애플리케이션 스레드 참여 없이) 속도가 매우 느리므로(CPU 클록 주기에 비해) 생성된 스레드는 할 일이 없습니다
. 게다가 우리 서비스는 추가 스레드 전환
오버헤드도 부담해야 합니다. 이러한 언어와 달리 Node는 각 요청에 대해 스레드를 생성하지 않습니다. 모든 요청 처리
는 기본 스레드에서 발생하므로 스레드 전환
에 대한 오버헤드가 없으며 또한 스레드 풀
을 통해 이러한 I/O
작업을 비동기적으로 처리한 다음 메인 스레드의 실행을 차단하지 않도록 결과를 이벤트 형식으로 메인 스레드에 알립니다. 이론적으로
더 효율적입니다. 여기서는 Node가 이론상 더 빠르다고 말했지만 실제로는 반드시 더 빠르지는 않습니다. 이는 실제로 서비스 성능이 여러 측면에 의해 영향을 받기 때문입니다. 여기서는 동시성 모델
요소만 고려하고 런타임 소비와 같은 다른 요소도 서비스 성능에 영향을 미치기 때문입니다. 예를 들어 JavaScript
는 동적 언어이고 데이터 유형은 런타임에 추론되어야 하는 반면, Golang
및 Java
는 정적 언어입니다. 그리고 해당 데이터 유형은 컴파일 타임에 결정되므로 실제로 더 빠르게 실행되고 메모리를 덜 차지할 수 있습니다. 🎜CPU 집약적인
작업을 처리해야 할 경우 메인 스레드가 차단됩니다. CPU 집약적인 작업의 예를 살펴보겠습니다. 🎜// node/cpu_intensive.js const http = require('http') const url = require('url') const hardWork = () => { // 100亿次毫无意义的计算 for (let i = 0; i { const urlParsed = url.parse(req.url, true) if (urlParsed.pathname === '/hard_work') { hardWork() resp.write('hard work') resp.end() } else if (urlParsed.pathname === '/easy_work') { resp.write('easy work') resp.end() } else { resp.end() } }) server.listen(8080, () => { console.log('server is up...') })
위 코드에서는 두 개의 인터페이스로 HTTP 서비스를 구현합니다. /hard_work
인터페이스는 hardWork
를 호출하기 때문에 CPU 집약적 인터페이스
입니다. 이 CPU 집약적
기능은 /easy_work
인터페이스가 매우 간단하지만 문자열을 클라이언트에 직접 반환하기만 하면 됩니다. hardWork
기능이 CPU 집약적
이라고 불리는 이유는 무엇입니까? 이는 I/O 작업을 수행하지 않고 CPU의 연산자
에서 i
에 대한 산술 연산을 수행하기 때문입니다. Node 서비스를 시작한 후 /hard_word
인터페이스를 호출하려고 합니다: /hard_work
接口是一个CPU密集型接口
,因为它调用了hardWork
这个CPU密集型
函数,而/easy_work
这个接口则很简单,直接返回一个字符串给客户端就可以了。为什么说hardWork
函数是CPU密集型
的呢?这是因为它都是在CPU的运算器
里面对i
进行算术运算而没有进行任何I/O操作。启动完我们的Node服务后,我们试着调用一下/hard_word
接口:
我们可以看到/hard_work
接口是会卡住的,这是因为它需要进行大量的CPU
计算,所以需要比较久的时间才会执行完。而这个时候我们再看一下/easy_work
这个接口有没有影响:
我们发现在/hard_work
占用了CPU资源之后,无辜的/easy_work
接口也被卡死了。原因就是hardWork
函数阻塞了Node的主线程导致/easy_work
的逻辑不会被执行。这里值得一提的是,只有Node这种基于事件循环的单线程执行环境才会有这种问题,Java和Golang等Thread-Based语言是不会存在这种问题的。那如果我们的服务真的需要运行CPU密集型
任务怎么办?总不能换门语言吧?说好的All in JavaScript
呢?别着急,对于处理CPU密集型任务
,Node已经为我们准备好很多方案了,接下来就让我为大家介绍三种常用的方案,它们分别是: Cluster Module
,Child Process
和Worker Thread
。
Node很早(v0.8版本)就推出了Cluster模块。这个模块的作用就是通过一个父进程启动一群子进程来对网络请求进行负载均衡
。因为文章的篇幅限制我们不会细聊Cluster模块有哪些API,感兴趣的读者后面可以看看官方文档,这里我们直接看一下如何使用Cluster模块来优化上面CPU密集型的场景:
// node/cluster.js const cluster = require('cluster') const http = require('http') const url = require('url') // 获取CPU核数 const numCPUs = require('os').cpus().length const hardWork = () => { // 100亿次毫无意义的计算 for (let i = 0; i { console.log(`worker ${worker.process.pid} is online`) }) cluster.on('exit', (worker, code, signal) => { // 某个工作进程挂了之后,我们需要立马启动另外一个工作进程来替代 console.log(`worker ${worker.process.pid} exited with code ${code}, and signal ${signal}, start a new one...`) cluster.fork() }) } else { // 工作进程启动一个HTTP服务器 const server = http.createServer((req, resp) => { const urlParsed = url.parse(req.url, true) if (urlParsed.pathname === '/hard_work') { hardWork() resp.write('hard work') resp.end() } else if (urlParsed.pathname === '/easy_work') { resp.write('easy work') resp.end() } else { resp.end() } }) // 所有的工作进程都监听在同一个端口 server.listen(8080, () => { console.log(`worker ${process.pid} server is up...`) }) }
在上面的代码中我们根据当前设备的CPU核数使用cluster.fork
函数创建了同等数量的工作进程
,而且这些工作进程都是监听在8080
端口上面的。看到这里你或许会问所有的进程都监听在同一个端口会不会出现问题,这里其实是不会的,因为Cluster
模块底层会做一些工作让最终监听在8080
端口的是主进程
,而主进程是所有流量的入口
,它会接收HTTP连接并把它们打到不同的工作进程上面。话不多说,让我们运行一下这个node服务:
从上面的输出结果来看,cluster启动了10个worker(我的电脑是10核的)来处理web请求,这个时候我们再来请求一下/hard_work
这个接口:
我们发现这个请求还是卡死的,接着我们再来看看Cluster模块有没有解决其它请求也被阻塞
的问题:
我们可以看到前面9个请求
都是很顺利就返回结果的,可是到了第10个请求
我们的接口就卡住了,这是为什么呢?原因就是我们一共开了10个工作进程,主进程在将流量打到子进程的时候采用的默认负载均衡策略是round-robin
(轮流),因此第10个请求(其实是第11个,因为包括了第一个hard_work的请求)刚好回到第一个worker,而这个worker还没处理完hard_work
的任务,因此这个easy_work
的任务也就卡住了。cluster的负载均衡算法可以通过cluster.schedulingPolicy
/hard_work
인터페이스가 CPU를 많이 요구하기 때문에 중단되는 것을 볼 수 있습니다.
계산이므로 완료하는 데 시간이 오래 걸립니다. 이때 /easy_work
인터페이스가 어떤 영향을 미치는지 살펴보겠습니다. 🎜🎜🎜🎜 /hard_work
가 CPU 리소스를 점유한 후 무해한 /easy_work code> 인터페이스도 멈췄습니다. 그 이유는 <code>hardWork
함수가 Node의 메인 스레드를 차단하여 /easy_work
의 로직이 실행되지 않기 때문입니다. Node와 같은 이벤트 루프를 기반으로 하는 단일 스레드 실행 환경에서만 이 문제가 발생한다는 점을 여기서 언급할 가치가 있습니다. Java 및 Golang과 같은 스레드 기반 언어에는 이 문제가 없습니다. 그렇다면 우리 서비스가 실제로 CPU 집약적인
작업을 실행해야 한다면 어떻게 될까요? 언어는 바꿀 수 없잖아요? JavaScript의 모든 것
은 어떻습니까? 걱정하지 마세요. Node는 CPU 집약적인 작업
을 처리할 수 있도록 많은 솔루션을 준비했습니다. 다음으로 일반적으로 사용되는 세 가지 솔루션을 소개하겠습니다. 클러스터 모듈
, 하위 프로세스
및 작업자 스레드
. 🎜상위 프로세스에서 하위 프로세스 그룹을 시작
하여 네트워크 요청의 부하를 분산하는 것입니다. 기사의 길이 제한으로 인해 클러스터 모듈의 API에 대해 자세히 설명하지 않습니다. 관심 있는 독자는 나중에 공식 문서를 읽을 수 있습니다. 여기서는 클러스터 모듈을 사용하여 위 CPU를 최적화하는 방법을 직접 살펴보겠습니다. -집약적 시나리오: 🎜// node/master_process.js const { fork } = require('child_process') const http = require('http') const url = require('url') const server = http.createServer((req, resp) => { const urlParsed = url.parse(req.url, true) if (urlParsed.pathname === '/hard_work') { // 对于hard_work请求我们启动一个子进程来处理 const child = fork('./child_process') // 告诉子进程开始工作 child.send('START') // 接收子进程返回的数据,并且返回给客户端 child.on('message', () => { resp.write('hard work') resp.end() }) } else if (urlParsed.pathname === '/easy_work') { // 简单工作都在主进程进行 resp.write('easy work') resp.end() } else { resp.end() } }) server.listen(8080, () => { console.log('server is up...') })🎜 위의 코드에서는
cluster.fork
함수를 사용하여 클러스터의 CPU 코어 수에 따라 동일한 수의 작업자 프로세스
를 생성합니다. 현재 장치이며 이러한 작업자 프로세스는 모두 포트의 8080
에서 수신 대기합니다. 이를 보면 모든 프로세스가 동일한 포트에서 수신 대기하는 경우 문제가 있는지 물을 수 있습니다. 실제로 Cluster
모듈의 하위 계층이 일부 작업을 수행하므로 여기서는 문제가 없습니다. 8080
포트가 메인 프로세스
이고, 메인 프로세스가 모든 트래픽의 입구
로 HTTP를 수신하게 됩니다. 연결하고 이를 다른 작업자 프로세스로 라우팅합니다. 더 이상 고민하지 말고 다음 노드 서비스를 실행해 보겠습니다. 🎜🎜🎜🎜위 출력에서 클러스터는 웹 요청을 처리하기 위해 10개의 작업자(내 컴퓨터에는 10개의 코어가 있음)를 시작했습니다. 이번에는 /hard_work
이 인터페이스를 다시 요청하겠습니다. :🎜🎜🎜🎜 우리는 이 요청이 여전히 정체되어 있음을 발견했으며, 클러스터 모듈이 다른 요청도 차단
되는 문제를 해결할 수 있는지 확인할 것입니다:🎜🎜🎜🎜처음 9개의 요청
을 볼 수 있습니다 모두 원활하게 결과를 반환했지만 10번째 요청
에 도달했을 때 인터페이스가 중단되었습니다. 이유는 무엇입니까? 그 이유는 총 10개의 작업자 프로세스를 오픈했기 때문입니다. 기본 프로세스에서 하위 프로세스로 트래픽을 보낼 때 사용하는 기본 로드 밸런싱 전략은 라운드 로빈
(순차)이므로 10번째 요청입니다. (실제로는 첫 번째 hard_work 요청이 포함되어 있으므로 11번째입니다.) 첫 번째 워커로 돌아가고 이 워커는 hard_work
작업 처리를 완료하지 않았으므로 이 easy_work
작업이 중단되었습니다. 클러스터의 로드 밸런싱 알고리즘은 cluster.schedulingPolicy
를 통해 수정할 수 있습니다. 관심 있는 독자는 공식 문서를 읽어보세요. 🎜从上面的结果来看Cluster Module似乎解决了一部分
我们的问题,可是还是有一些请求受到了影响。那么Cluster Module在实际开发里面能不能被用来解决这个CPU密集型
任务的问题呢?我的意见是:看情况。如果你的CPU密集型接口调用不频繁
而且运算时间不会太长
,你完全可以使用这种Cluster Module来优化。可是如果你的接口调用频繁并且每个接口都很耗时间的话,可能你需要看一下采用Child Process
或者Worker Thread
的方案了。
最后我们总结一下Cluster Module有什么优点:
资源利用率高
:可以充分利用CPU的多核能力
来提升请求处理效率。API设计简单
:可以让你实现简单的负载均衡
和一定程度的高可用
。这里值得注意的是我说的是一定程度的高可用,这是因为Cluster Module的高可用是单机版的
,也就是当宿主机器挂了,你的服务也就挂了,因此更高的高可用肯定是使用分布式集群做的。进程之间高度独立
,避免某个进程发生系统错误导致整个服务不可用。优点说完了,我们再来说一下Cluster Module不好的地方:
资源消耗大
:每一个子进程都是独立的Node运行环境
,也可以理解为一个独立的Node程序,因此占用的资源也是巨大的
。进程通信开销大
:子进程之间的通信通过跨进程通信(IPC)
来进行,如果数据共享频繁是一笔比较大的开销。没能完全解决CPU密集任务
:处理CPU密集型任务时还是有点抓紧见肘
。在Cluster Module中我们可以通过启动更多的子进程来将一些CPU密集型的任务负载均衡到不同的进程里面,从而避免其余接口卡死。可是你也看到了,这个办法治标不治本
,如果用户频繁调用CPU密集型的接口,那么还是会有一大部分请求会被卡死的。优化这个场景的另外一个方法就是child_process
模块。
Child Process
可以让我们启动子进程
来完成一些CPU密集型任务。我们先来看一下主进程master_process.js
的代码:
// node/master_process.js const { fork } = require('child_process') const http = require('http') const url = require('url') const server = http.createServer((req, resp) => { const urlParsed = url.parse(req.url, true) if (urlParsed.pathname === '/hard_work') { // 对于hard_work请求我们启动一个子进程来处理 const child = fork('./child_process') // 告诉子进程开始工作 child.send('START') // 接收子进程返回的数据,并且返回给客户端 child.on('message', () => { resp.write('hard work') resp.end() }) } else if (urlParsed.pathname === '/easy_work') { // 简单工作都在主进程进行 resp.write('easy work') resp.end() } else { resp.end() } }) server.listen(8080, () => { console.log('server is up...') })
在上面的代码中对于/hard_work
接口的请求,我们会通过fork
函数开启一个新的子进程
来处理,当子进程处理完毕我们拿到数据后就给客户端返回结果。这里值得注意的是当子进程完成任务后我没有释放子进程的资源,在实际项目里面我们也不应该频繁创建和销毁子进程因为这个消耗也是很大的,更好的做法是使用进程池
。下面是子进程
(child_process.js)的实现逻辑:
// node/child_process.js const hardWork = () => { // 100亿次毫无意义的计算 for (let i = 0; i { if (message === 'START') { // 开始干活 hardWork() // 干完活就通知子进程 process.send(message) } })
子进程的代码也很简单,它在启动后会通过process.on
的方式监听来自父进程的消息,在接收到开始命令后进行CPU密集型
的计算,得出结果后返回给父进程。
运行上面master_process.js
的代码,我们可以发现即使调用了/hard_work
接口,我们还是可以任意调用/easy_work
接口并且马上得到响应的,此处没有截图,过程大家脑补一下就可以了。
除了fork
函数,child_process
还提供了诸如exec
和spawn
等函数来启动子进程,并且这些进程可以执行任何的shell
命令而不只是局限于Node脚本,有兴趣的读者后面可以通过官方文档了解一下,这里就不过多介绍了。
最后让我们来总结一下Child Process
的优点有哪些:
灵活
:不只局限于Node进程,我们可以在子进程里面执行任何的shell
命令。这个其实是一个很大的优点,假如我们的CPU密集型操作是用其它语言实现
的(例如c语言处理图像),而我们不想使用Node或者C++ Binding重新实现一遍的话我们就可以通过shell
命令调用其它语言的程序,并且通过标准输入输出
和它们进行通信从而得到结果。细粒度的资源控制
:不像Cluster Module,Child Process方案可以按照实际对CPU密集型计算的需求大小动态调整子进程的个数,做到资源的细粒度控制,因此它理论上
是可以解决Cluster Module解决不了的CPU密集型接口调用频繁
的问题。不过Child Process的缺点也很明显:
资源消耗巨大
:上面说它可以对资源进行细粒度控制
的优点时,也说了它只是理论上
可以解决CPU密集型接口频繁调用的问题
,这是因为实际场景下我们的资源也是有限的
,而每一个Child Process都是一个独立的操作系统进程,会消耗巨大的资源。因此对于频繁调用的接口我们需要采取能耗更低的方案也就是下面我会说的Worker Thread
。进程通信麻烦
:如果启动的子进程也是Node应用的话还好办点,因为有内置的API
来和父进程通信,如果子进程不是Node应用的话,我们只能通过标准输入输出
或者其它方式来进行进程间通信,这是一件很麻烦的事。无论是Cluster Module还是Child Process其实都是基于子进程的,它们都有一个巨大的缺点就是资源消耗大
。为了解决这个问题Node从v10.5.0版本(v12.11.0 stable)开始就支持了worker_threads
模块,worker_thread是Node对于CPU密集型操作
的轻量级的线程解决方案
。
Node的Worker Thread
和其它语言的thread是一样的,那就是并发
地运行你的代码。这里要注意是并发
而不是并行
。并行
只是意味着一段时间内多件事情同时发生
,而并发
是某个时间点多件事情同时发生
。一个典型的并行
例子就是React的Fiber架构
,因为它是通过时分复用
的方式来调度不同的任务来避免React渲染
阻塞浏览器的其它行为的,所以本质上它所有的操作还是在同一个操作系统线程
执行的。不过这里值得注意的是:虽然并发
强调多个任务同时执行,在单核CPU的情况下,并发会退化为并行
。这是因为CPU同一个时刻只能做一件事,当你有多个线程需要执行的话
就需要通过资源抢占
的方式来时分复用
执行某些任务。不过这都是操作系统需要关心的东西,和我们没什么关系了。
上面说了Node的Worker Thead和其他语言线程的thread类似的地方,接着我们来看一下它们不一样的地方。如果你使用过其它语言的多线程编程方式,你会发现Node的多线程和它们很不一样,因为Node多线程数据共享起来
实在是太麻烦了
!Node是不允许你通过共享内存变量
的方式来共享数据的,你只能用ArrayBuffer
或者SharedArrayBuffer
的方式来进行数据的传递和共享。虽然说这很不方便,不过这也让我们不需要过多考虑多线程环境下数据安全等一系列问题
,可以说有好处也有坏处吧。
接着我们来看一下如何使用Worker Thread
来处理上面的CPU密集型任务,先看一下主线程(master_thread.js)的代码:
// node/master_thread.js const { Worker } = require('worker_threads') const http = require('http') const url = require('url') const server = http.createServer((req, resp) => { const urlParsed = url.parse(req.url, true) if (urlParsed.pathname === '/hard_work') { // 对于每一个hard_work接口,我们都启动一个子线程来处理 const worker = new Worker('./child_process') // 告诉子线程开始任务 worker.postMessage('START') worker.on('message', () => { // 在收到子线程回复后返回结果给客户端 resp.write('hard work') resp.end() }) } else if (urlParsed.pathname === '/easy_work') { // 其它简单操作都在主线程执行 resp.write('easy work') resp.end() } else { resp.end() } }) server.listen(8080, () => { console.log('server is up...') })
在上面的代码中,我们的服务器每次接收到/hard_work
请求都会通过new Worker
的方式启动一个Worker
线程来处理,在worker处理完任务之后我们再将结果返回给客户端,这个过程是异步的。接着再看一下子线程
(worker_thead.js)的代码实现:
// node/worker_thread.js const { parentPort } = require('worker_threads') const hardWork = () => { // 100亿次毫无意义的计算 for (let i = 0; i { if (message === 'START') { hardWork() parentPort.postMessage() } })
在上面的代码中,worker thread在接收到主线程的命令后开始执行CPU密集型
操作,最后通过parentPort.postMessage
的方式告知父线程任务已经完成,从API上看父子线程通信还是挺方便的。
마지막으로 워커 스레드의 장점과 단점을 정리해 보겠습니다. 우선 장점은 다음과 같습니다.
낮은 리소스 소비
: 클러스터 모듈 및 하위 프로세스의 프로세스 기반 접근 방식과 달리 Worker Thread는 보다 가벼운 스레드를 기반으로 하므로 리소스가 오버헤드는 비교적 작습니다
. 그러나 Sparrow는 작고 잘 갖춰져
있으며 각 Worker Thread
에는 자체적인 독립적인 v8 엔진 인스턴스
와 이벤트 루프
가 있습니다. 코드> >체계적입니다. 이는 메인 스레드가 중단
되더라도 작업자 스레드
가 계속 작동할 수 있음을 의미하며 이를 기반으로 실제로 많은 흥미로운 작업을 수행할 수 있습니다. 资源消耗小
:不同于Cluster Module和Child Process基于进程的方式,Worker Thread是基于更加轻量级的线程的,所以它的资源开销是相对较小的
。不过麻雀虽小五脏俱全
,每个Worker Thread
都是有自己独立的v8引擎实例
和事件循环
系统的。这也就是说即使主线程卡死
我们的Worker Thread
也是可以继续工作的,基于这个其实我们可以做很多有趣的事情。父子线程通信方便高效
:和前面两种方式不一样,Worker Thread不需要通过IPC通信,所有数据都是在进程内部实现共享和传递的。不过Worker Thread也不是完美的:
线程隔离性低
:由于子线程不是在一个独立的环境
执行的,所以某个子线程挂了还是会影响到其它线程,在这种情况下,你需要做一些额外的措施来保护其余线程不受影响。线程数据共享实现麻烦
:和其它后端语言比起来,Node的数据共享还是比较麻烦的,不过这其实也避免了它需要考虑很多多线程下数据安全的问题。在本篇文章中我为大家介绍了Node为什么适合做I/O密集型应用而很难处理CPU密集型任务的原因,并且为大家提供了三个可选方案来在实际开发中处理CPU密集型任务。每个方案其实都有利有弊,我们一定要根据实际情况进行选择,永远不要为了要用某个技术而一定要采取某个方案
아버지-자식 스레드 통신은 편리하고 효율적입니다
: 이전 두 가지 방법과 달리 작업자 스레드는 IPC를 통해 통신할 필요가 없으며 모든 데이터가 프로세스 내에서 공유 및 전송됩니다.
낮은 스레드 격리
: 하위 스레드가 독립적인 환경
에서 실행되지 않기 때문에, 따라서 특정 하위 스레드가 중단되면 여전히 다른 스레드에 영향을 미칩니다. 이 경우 나머지 스레드가 영향을 받지 않도록 보호하기 위해 몇 가지 추가 조치를 취해야 합니다.
스레드 데이터 공유는 번거롭습니다
: 다른 백엔드 언어와 비교할 때 Node의 데이터 공유는 여전히 더 번거롭지만 이는 실제로 다중 환경에서 많은 데이터 보안을 고려할 필요를 피합니다. 스레딩. 단지 특정 기술을 사용하기 위해 특정 솔루션을 채택하지 마십시오
. 🎜🎜노드 관련 지식을 더 보려면 🎜nodejs 튜토리얼🎜을 방문하세요! 🎜위 내용은 CPU 집약적인 작업을 처리하는 Node의 방법에 대한 간략한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!