溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node處理CPU密集型任務(wù)的方法是什么

發(fā)布時(shí)間:2022-09-14 09:41:49 來(lái)源:億速云 閱讀:121 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“Node處理CPU密集型任務(wù)的方法是什么”,在日常操作中,相信很多人在Node處理CPU密集型任務(wù)的方法是什么問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Node處理CPU密集型任務(wù)的方法是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

我們?nèi)粘9ぷ髦谢蚨嗷蛏俾犝f(shuō)過(guò)以下的話:

Node是一個(gè)非阻塞I/O(non-blocking I/O)和事件驅(qū)動(dòng)(event-driven)的JavaScript運(yùn)行環(huán)境(runtime),所以它非常適合用來(lái)構(gòu)建I/O密集型應(yīng)用,例如Web服務(wù)等。

不知道當(dāng)你聽到類似的話時(shí)會(huì)不會(huì)有和我一樣的疑惑:單線程的Node為什么適合用來(lái)開發(fā)I/O密集型應(yīng)用?按道理來(lái)說(shuō)不是那些支持多線程的語(yǔ)言(例如Java和Golang)做這些工作更加有優(yōu)勢(shì)嗎?

要搞明白上面的問題,我們需要知道Node的單線程指的是什么。

Node不是單線程的

其實(shí)我們說(shuō)Node是單線程的,說(shuō)的只是我們的JavaScript代碼是在同一個(gè)線程(我們可以叫它主線程)里面運(yùn)行的,而不是說(shuō)Node只有一個(gè)線程在工作。實(shí)際上Node底層會(huì)使用libuv的多線程能力將一部分工作(基本都是I/O相關(guān)操作)放在一些主線程之外的線程里面執(zhí)行,當(dāng)這些任務(wù)完成后再以回調(diào)函數(shù)的方式將結(jié)果返回到主線程的JavaScript執(zhí)行環(huán)境??梢钥纯词疽鈭D:

Node處理CPU密集型任務(wù)的方法是什么

注: 上圖是Node事件循環(huán)(Event Loop)的簡(jiǎn)化版,實(shí)際上完整的事件循環(huán)會(huì)有更多的階段例如timers等。

Node適合做I/O密集型應(yīng)用

從上面的分析中我們知道Node會(huì)將所有的I/O操作通過(guò)libuv的多線程能力分散到不同的線程里面執(zhí)行,其余的操作都放在主線程里面執(zhí)行。那么為什么這種做法就比Java或者Golang等其它語(yǔ)言更適合做I/O密集型應(yīng)用呢?我們以開發(fā)Web服務(wù)為例,Java和Golang等主流后端編程語(yǔ)言的并發(fā)模型是基于線程(Thread-Based)的,這也就意味他們對(duì)于每一個(gè)網(wǎng)絡(luò)請(qǐng)求都會(huì)創(chuàng)建一個(gè)單獨(dú)的線程來(lái)處理??墒菍?duì)于Web應(yīng)用來(lái)說(shuō),主要還是對(duì)數(shù)據(jù)庫(kù)的增刪改查,或者請(qǐng)求其它外部服務(wù)等網(wǎng)絡(luò)I/O操作,而這些操作最后都是交給操作系統(tǒng)的系統(tǒng)調(diào)用來(lái)處理的(無(wú)需應(yīng)用線程參與),并且十分緩慢(相對(duì)于CPU時(shí)鐘周期來(lái)說(shuō)),因此被創(chuàng)建出來(lái)的線程大多數(shù)時(shí)間是無(wú)事可做的而且我們的服務(wù)還要承擔(dān)額外的線程切換開銷。和這些語(yǔ)言不一樣的是Node沒有為每個(gè)請(qǐng)求都創(chuàng)建一個(gè)線程,所有請(qǐng)求的處理都發(fā)生在主線程中,因此沒有了線程切換的開銷,并且它還會(huì)通過(guò)線程池的形式異步處理這些I/O操作,然后通過(guò)事件的形式告訴主線程結(jié)果從而避免阻塞主線程的執(zhí)行,因此它理論上是更高效的。這里值得注意的是我只是說(shuō)Node理論上是更快的,實(shí)際上真不一定。這是因?yàn)楝F(xiàn)實(shí)中一個(gè)服務(wù)的性能會(huì)受到很多方面的影響,我們這里只是考慮了并發(fā)模型這一個(gè)因素,而其它因素例如運(yùn)行時(shí)消耗也會(huì)影響到服務(wù)的性能,舉個(gè)例子,JavaScript是動(dòng)態(tài)語(yǔ)言,數(shù)據(jù)的類型需要在運(yùn)行時(shí)進(jìn)行推斷,而GolangJava都是靜態(tài)語(yǔ)言它們的數(shù)據(jù)類型在編譯時(shí)就可以確定,所以它們實(shí)際執(zhí)行起來(lái)可能會(huì)更快,占用內(nèi)存也會(huì)更少。

Node不適合做CPU密集型任務(wù)

上面我們提到Node除了I/O相關(guān)的操作其余操作都會(huì)在主線程里面執(zhí)行,所以當(dāng)Node要處理一些CPU密集型的任務(wù)時(shí),主線程會(huì)被阻塞住。我們來(lái)看一個(gè)CPU密集型任務(wù)的例子:

// node/cpu_intensive.js

const http = require('http')
const url = require('url')

const hardWork = () => {
  // 100億次毫無(wú)意義的計(jì)算
  for (let i = 0; i < 10000000000; i++) {}
}

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('server is up...')
})

在上面的代碼中我們實(shí)現(xiàn)了擁有兩個(gè)接口的HTTP服務(wù):/hard_work接口是一個(gè)CPU密集型接口,因?yàn)樗{(diào)用了hardWork這個(gè)CPU密集型函數(shù),而/easy_work這個(gè)接口則很簡(jiǎn)單,直接返回一個(gè)字符串給客戶端就可以了。為什么說(shuō)hardWork函數(shù)是CPU密集型的呢?這是因?yàn)樗际窃贑PU的運(yùn)算器里面對(duì)i進(jìn)行算術(shù)運(yùn)算而沒有進(jìn)行任何I/O操作。啟動(dòng)完我們的Node服務(wù)后,我們?cè)囍{(diào)用一下/hard_word接口:

Node處理CPU密集型任務(wù)的方法是什么

我們可以看到/hard_work接口是會(huì)卡住的,這是因?yàn)樗枰M(jìn)行大量的CPU計(jì)算,所以需要比較久的時(shí)間才會(huì)執(zhí)行完。而這個(gè)時(shí)候我們?cè)倏匆幌?code>/easy_work這個(gè)接口有沒有影響:

Node處理CPU密集型任務(wù)的方法是什么

我們發(fā)現(xiàn)在/hard_work占用了CPU資源之后,無(wú)辜的/easy_work接口也被卡死了。原因就是hardWork函數(shù)阻塞了Node的主線程導(dǎo)致/easy_work的邏輯不會(huì)被執(zhí)行。這里值得一提的是,只有Node這種基于事件循環(huán)的單線程執(zhí)行環(huán)境才會(huì)有這種問題,Java和Golang等Thread-Based語(yǔ)言是不會(huì)存在這種問題的。那如果我們的服務(wù)真的需要運(yùn)行CPU密集型任務(wù)怎么辦?總不能換門語(yǔ)言吧?說(shuō)好的All in JavaScript呢?別著急,對(duì)于處理CPU密集型任務(wù),Node已經(jīng)為我們準(zhǔn)備好很多方案了,接下來(lái)就讓我為大家介紹三種常用的方案,它們分別是: Cluster Module,Child ProcessWorker Thread。

Cluster Module

概念介紹

Node很早(v0.8版本)就推出了Cluster模塊。這個(gè)模塊的作用就是通過(guò)一個(gè)父進(jìn)程啟動(dòng)一群子進(jìn)程來(lái)對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行負(fù)載均衡。因?yàn)槲恼碌钠拗莆覀儾粫?huì)細(xì)聊Cluster模塊有哪些API,感興趣的讀者后面可以看看官方文檔,這里我們直接看一下如何使用Cluster模塊來(lái)優(yōu)化上面CPU密集型的場(chǎng)景:

// node/cluster.js

const cluster = require('cluster')
const http = require('http')
const url = require('url')

// 獲取CPU核數(shù)
const numCPUs = require('os').cpus().length

const hardWork = () => {
  // 100億次毫無(wú)意義的計(jì)算
  for (let i = 0; i < 10000000000; i++) {}
}

// 判斷當(dāng)前是否是主進(jìn)程
if (cluster.isMaster) {
  // 根據(jù)當(dāng)前機(jī)器的CPU核數(shù)創(chuàng)建同等數(shù)量的工作進(jìn)程
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork()
  }

  cluster.on('online', (worker) => {
    console.log(`worker ${worker.process.pid} is online`)
  })

  cluster.on('exit', (worker, code, signal) => {
    // 某個(gè)工作進(jìn)程掛了之后,我們需要立馬啟動(dòng)另外一個(gè)工作進(jìn)程來(lái)替代
    console.log(`worker ${worker.process.pid} exited with code ${code}, and signal ${signal}, start a new one...`)
    cluster.fork()
  })
} else {
  // 工作進(jìn)程啟動(dòng)一個(gè)HTTP服務(wù)器
  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()
    }
  })
  
  // 所有的工作進(jìn)程都監(jiān)聽在同一個(gè)端口
  server.listen(8080, () => {
    console.log(`worker ${process.pid} server is up...`)
  })
}

在上面的代碼中我們根據(jù)當(dāng)前設(shè)備的CPU核數(shù)使用cluster.fork函數(shù)創(chuàng)建了同等數(shù)量的工作進(jìn)程,而且這些工作進(jìn)程都是監(jiān)聽在8080端口上面的??吹竭@里你或許會(huì)問所有的進(jìn)程都監(jiān)聽在同一個(gè)端口會(huì)不會(huì)出現(xiàn)問題,這里其實(shí)是不會(huì)的,因?yàn)?code>Cluster模塊底層會(huì)做一些工作讓最終監(jiān)聽在8080端口的是主進(jìn)程,而主進(jìn)程是所有流量的入口,它會(huì)接收HTTP連接并把它們打到不同的工作進(jìn)程上面。話不多說(shuō),讓我們運(yùn)行一下這個(gè)node服務(wù):

Node處理CPU密集型任務(wù)的方法是什么

從上面的輸出結(jié)果來(lái)看,cluster啟動(dòng)了10個(gè)worker(我的電腦是10核的)來(lái)處理web請(qǐng)求,這個(gè)時(shí)候我們?cè)賮?lái)請(qǐng)求一下/hard_work這個(gè)接口:

Node處理CPU密集型任務(wù)的方法是什么

我們發(fā)現(xiàn)這個(gè)請(qǐng)求還是卡死的,接著我們?cè)賮?lái)看看Cluster模塊有沒有解決其它請(qǐng)求也被阻塞的問題:

Node處理CPU密集型任務(wù)的方法是什么

我們可以看到前面9個(gè)請(qǐng)求都是很順利就返回結(jié)果的,可是到了第10個(gè)請(qǐng)求我們的接口就卡住了,這是為什么呢?原因就是我們一共開了10個(gè)工作進(jìn)程,主進(jìn)程在將流量打到子進(jìn)程的時(shí)候采用的默認(rèn)負(fù)載均衡策略是round-robin(輪流),因此第10個(gè)請(qǐng)求(其實(shí)是第11個(gè),因?yàn)榘说谝粋€(gè)hard_work的請(qǐng)求)剛好回到第一個(gè)worker,而這個(gè)worker還沒處理完hard_work的任務(wù),因此這個(gè)easy_work的任務(wù)也就卡住了。cluster的負(fù)載均衡算法可以通過(guò)cluster.schedulingPolicy來(lái)修改,有興趣的讀者可以看一下官方文檔。

從上面的結(jié)果來(lái)看Cluster Module似乎解決了一部分我們的問題,可是還是有一些請(qǐng)求受到了影響。那么Cluster Module在實(shí)際開發(fā)里面能不能被用來(lái)解決這個(gè)CPU密集型任務(wù)的問題呢?我的意見是:看情況。如果你的CPU密集型接口調(diào)用不頻繁而且運(yùn)算時(shí)間不會(huì)太長(zhǎng),你完全可以使用這種Cluster Module來(lái)優(yōu)化??墒侨绻愕慕涌谡{(diào)用頻繁并且每個(gè)接口都很耗時(shí)間的話,可能你需要看一下采用Child Process或者Worker Thread的方案了。

Cluster Module的優(yōu)缺點(diǎn)

最后我們總結(jié)一下Cluster Module有什么優(yōu)點(diǎn):

  • 資源利用率高:可以充分利用CPU的多核能力來(lái)提升請(qǐng)求處理效率。

  • API設(shè)計(jì)簡(jiǎn)單:可以讓你實(shí)現(xiàn)簡(jiǎn)單的負(fù)載均衡一定程度的高可用。這里值得注意的是我說(shuō)的是一定程度的高可用,這是因?yàn)镃luster Module的高可用是單機(jī)版的,也就是當(dāng)宿主機(jī)器掛了,你的服務(wù)也就掛了,因此更高的高可用肯定是使用分布式集群做的。

  • 進(jìn)程之間高度獨(dú)立,避免某個(gè)進(jìn)程發(fā)生系統(tǒng)錯(cuò)誤導(dǎo)致整個(gè)服務(wù)不可用。

優(yōu)點(diǎn)說(shuō)完了,我們?cè)賮?lái)說(shuō)一下Cluster Module不好的地方:

  • 資源消耗大:每一個(gè)子進(jìn)程都是獨(dú)立的Node運(yùn)行環(huán)境,也可以理解為一個(gè)獨(dú)立的Node程序,因此占用的資源也是巨大的。

  • 進(jìn)程通信開銷大:子進(jìn)程之間的通信通過(guò)跨進(jìn)程通信(IPC)來(lái)進(jìn)行,如果數(shù)據(jù)共享頻繁是一筆比較大的開銷。

  • 沒能完全解決CPU密集任務(wù):處理CPU密集型任務(wù)時(shí)還是有點(diǎn)抓緊見肘。

Child Process

在Cluster Module中我們可以通過(guò)啟動(dòng)更多的子進(jìn)程來(lái)將一些CPU密集型的任務(wù)負(fù)載均衡到不同的進(jìn)程里面,從而避免其余接口卡死??墒悄阋部吹搅?,這個(gè)辦法治標(biāo)不治本,如果用戶頻繁調(diào)用CPU密集型的接口,那么還是會(huì)有一大部分請(qǐng)求會(huì)被卡死的。優(yōu)化這個(gè)場(chǎng)景的另外一個(gè)方法就是child_process模塊。

概念介紹

Child Process可以讓我們啟動(dòng)子進(jìn)程來(lái)完成一些CPU密集型任務(wù)。我們先來(lái)看一下主進(jìn)程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') {
    // 對(duì)于hard_work請(qǐng)求我們啟動(dòng)一個(gè)子進(jìn)程來(lái)處理
    const child = fork('./child_process')
    // 告訴子進(jìn)程開始工作
    child.send('START')
    
    // 接收子進(jìn)程返回的數(shù)據(jù),并且返回給客戶端
    child.on('message', () => {
      resp.write('hard work')
      resp.end()
    })
  } else if (urlParsed.pathname === '/easy_work') {
    // 簡(jiǎn)單工作都在主進(jìn)程進(jìn)行
    resp.write('easy work')
    resp.end()
  } else {
    resp.end()
  }
})

server.listen(8080, () => {
  console.log('server is up...')
})

在上面的代碼中對(duì)于/hard_work接口的請(qǐng)求,我們會(huì)通過(guò)fork函數(shù)開啟一個(gè)新的子進(jìn)程來(lái)處理,當(dāng)子進(jìn)程處理完畢我們拿到數(shù)據(jù)后就給客戶端返回結(jié)果。這里值得注意的是當(dāng)子進(jìn)程完成任務(wù)后我沒有釋放子進(jìn)程的資源,在實(shí)際項(xiàng)目里面我們也不應(yīng)該頻繁創(chuàng)建和銷毀子進(jìn)程因?yàn)檫@個(gè)消耗也是很大的,更好的做法是使用進(jìn)程池。下面是子進(jìn)程(child_process.js)的實(shí)現(xiàn)邏輯:

// node/child_process.js

const hardWork = () => {
  // 100億次毫無(wú)意義的計(jì)算
  for (let i = 0; i < 10000000000; i++) {}
}

process.on('message', (message) => {
  if (message === 'START') {
    // 開始干活
    hardWork()
    // 干完活就通知子進(jìn)程
    process.send(message)
  }
})

子進(jìn)程的代碼也很簡(jiǎn)單,它在啟動(dòng)后會(huì)通過(guò)process.on的方式監(jiān)聽來(lái)自父進(jìn)程的消息,在接收到開始命令后進(jìn)行CPU密集型的計(jì)算,得出結(jié)果后返回給父進(jìn)程。

運(yùn)行上面master_process.js的代碼,我們可以發(fā)現(xiàn)即使調(diào)用了/hard_work接口,我們還是可以任意調(diào)用/easy_work接口并且馬上得到響應(yīng)的,此處沒有截圖,過(guò)程大家腦補(bǔ)一下就可以了。

除了fork函數(shù),child_process還提供了諸如execspawn等函數(shù)來(lái)啟動(dòng)子進(jìn)程,并且這些進(jìn)程可以執(zhí)行任何的shell命令而不只是局限于Node腳本,有興趣的讀者后面可以通過(guò)官方文檔了解一下,這里就不過(guò)多介紹了。

Child Process的優(yōu)缺點(diǎn)

最后讓我們來(lái)總結(jié)一下Child Process的優(yōu)點(diǎn)有哪些:

  • 靈活:不只局限于Node進(jìn)程,我們可以在子進(jìn)程里面執(zhí)行任何的shell命令。這個(gè)其實(shí)是一個(gè)很大的優(yōu)點(diǎn),假如我們的CPU密集型操作是用其它語(yǔ)言實(shí)現(xiàn)的(例如c語(yǔ)言處理圖像),而我們不想使用Node或者C++ Binding重新實(shí)現(xiàn)一遍的話我們就可以通過(guò)shell命令調(diào)用其它語(yǔ)言的程序,并且通過(guò)標(biāo)準(zhǔn)輸入輸出和它們進(jìn)行通信從而得到結(jié)果。

  • 細(xì)粒度的資源控制:不像Cluster Module,Child Process方案可以按照實(shí)際對(duì)CPU密集型計(jì)算的需求大小動(dòng)態(tài)調(diào)整子進(jìn)程的個(gè)數(shù),做到資源的細(xì)粒度控制,因此它理論上是可以解決Cluster Module解決不了的CPU密集型接口調(diào)用頻繁的問題。

不過(guò)Child Process的缺點(diǎn)也很明顯:

  • 資源消耗巨大:上面說(shuō)它可以對(duì)資源進(jìn)行細(xì)粒度控制的優(yōu)點(diǎn)時(shí),也說(shuō)了它只是理論上可以解決CPU密集型接口頻繁調(diào)用的問題,這是因?yàn)閷?shí)際場(chǎng)景下我們的資源也是有限的,而每一個(gè)Child Process都是一個(gè)獨(dú)立的操作系統(tǒng)進(jìn)程,會(huì)消耗巨大的資源。因此對(duì)于頻繁調(diào)用的接口我們需要采取能耗更低的方案也就是下面我會(huì)說(shuō)的Worker Thread。

  • 進(jìn)程通信麻煩:如果啟動(dòng)的子進(jìn)程也是Node應(yīng)用的話還好辦點(diǎn),因?yàn)橛?code>內(nèi)置的API來(lái)和父進(jìn)程通信,如果子進(jìn)程不是Node應(yīng)用的話,我們只能通過(guò)標(biāo)準(zhǔn)輸入輸出或者其它方式來(lái)進(jìn)行進(jìn)程間通信,這是一件很麻煩的事。

Worker Thread

無(wú)論是Cluster Module還是Child Process其實(shí)都是基于子進(jìn)程的,它們都有一個(gè)巨大的缺點(diǎn)就是資源消耗大。為了解決這個(gè)問題Node從v10.5.0版本(v12.11.0 stable)開始就支持了worker_threads模塊,worker_thread是Node對(duì)于CPU密集型操作輕量級(jí)的線程解決方案。

概念介紹

Node的Worker Thread和其它語(yǔ)言的thread是一樣的,那就是并發(fā)地運(yùn)行你的代碼。這里要注意是并發(fā)而不是并行并行只是意味著一段時(shí)間內(nèi)多件事情同時(shí)發(fā)生,而并發(fā)某個(gè)時(shí)間點(diǎn)多件事情同時(shí)發(fā)生。一個(gè)典型的并行例子就是React的Fiber架構(gòu),因?yàn)樗峭ㄟ^(guò)時(shí)分復(fù)用的方式來(lái)調(diào)度不同的任務(wù)來(lái)避免React渲染阻塞瀏覽器的其它行為的,所以本質(zhì)上它所有的操作還是在同一個(gè)操作系統(tǒng)線程執(zhí)行的。不過(guò)這里值得注意的是:雖然并發(fā)強(qiáng)調(diào)多個(gè)任務(wù)同時(shí)執(zhí)行,在單核CPU的情況下,并發(fā)會(huì)退化為并行。這是因?yàn)镃PU同一個(gè)時(shí)刻只能做一件事,當(dāng)你有多個(gè)線程需要執(zhí)行的話就需要通過(guò)資源搶占的方式來(lái)時(shí)分復(fù)用執(zhí)行某些任務(wù)。不過(guò)這都是操作系統(tǒng)需要關(guān)心的東西,和我們沒什么關(guān)系了。

上面說(shuō)了Node的Worker Thead和其他語(yǔ)言線程的thread類似的地方,接著我們來(lái)看一下它們不一樣的地方。如果你使用過(guò)其它語(yǔ)言的多線程編程方式,你會(huì)發(fā)現(xiàn)Node的多線程和它們很不一樣,因?yàn)镹ode多線程數(shù)據(jù)共享起來(lái)實(shí)在是太麻煩了!Node是不允許你通過(guò)共享內(nèi)存變量的方式來(lái)共享數(shù)據(jù)的,你只能用ArrayBuffer或者SharedArrayBuffer的方式來(lái)進(jìn)行數(shù)據(jù)的傳遞和共享。雖然說(shuō)這很不方便,不過(guò)這也讓我們不需要過(guò)多考慮多線程環(huán)境下數(shù)據(jù)安全等一系列問題,可以說(shuō)有好處也有壞處吧。

接著我們來(lái)看一下如何使用Worker Thread來(lái)處理上面的CPU密集型任務(wù),先看一下主線程(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') {
    // 對(duì)于每一個(gè)hard_work接口,我們都啟動(dòng)一個(gè)子線程來(lái)處理
    const worker = new Worker('./child_process')
    // 告訴子線程開始任務(wù)
    worker.postMessage('START')
    
    worker.on('message', () => {
      // 在收到子線程回復(fù)后返回結(jié)果給客戶端
      resp.write('hard work')
      resp.end()
    })
  } else if (urlParsed.pathname === '/easy_work') {
    // 其它簡(jiǎn)單操作都在主線程執(zhí)行
    resp.write('easy work')
    resp.end()
  } else {
    resp.end()
  }
})

server.listen(8080, () => {
  console.log('server is up...')
})

在上面的代碼中,我們的服務(wù)器每次接收到/hard_work請(qǐng)求都會(huì)通過(guò)new Worker的方式啟動(dòng)一個(gè)Worker線程來(lái)處理,在worker處理完任務(wù)之后我們?cè)賹⒔Y(jié)果返回給客戶端,這個(gè)過(guò)程是異步的。接著再看一下子線程(worker_thead.js)的代碼實(shí)現(xiàn):

// node/worker_thread.js

const { parentPort } = require('worker_threads')

const hardWork = () => {
  // 100億次毫無(wú)意義的計(jì)算
  for (let i = 0; i < 10000000000; i++) {}
}

parentPort.on('message', (message) => {
  if (message === 'START') {
    hardWork()
    parentPort.postMessage()
  }
})

在上面的代碼中,worker thread在接收到主線程的命令后開始執(zhí)行CPU密集型操作,最后通過(guò)parentPort.postMessage的方式告知父線程任務(wù)已經(jīng)完成,從API上看父子線程通信還是挺方便的。

Worker Thread的優(yōu)缺點(diǎn)

最后我們還是總結(jié)一下Worker Thread的優(yōu)缺點(diǎn)。首先我覺得它的優(yōu)點(diǎn)是:

  • 資源消耗小:不同于Cluster Module和Child Process基于進(jìn)程的方式,Worker Thread是基于更加輕量級(jí)的線程的,所以它的資源開銷是相對(duì)較小的。不過(guò)麻雀雖小五臟俱全,每個(gè)Worker Thread都是有自己獨(dú)立的v8引擎實(shí)例事件循環(huán)系統(tǒng)的。這也就是說(shuō)即使主線程卡死我們的Worker Thread也是可以繼續(xù)工作的,基于這個(gè)其實(shí)我們可以做很多有趣的事情。

  • 父子線程通信方便高效:和前面兩種方式不一樣,Worker Thread不需要通過(guò)IPC通信,所有數(shù)據(jù)都是在進(jìn)程內(nèi)部實(shí)現(xiàn)共享和傳遞的。

不過(guò)Worker Thread也不是完美的:

  • 線程隔離性低:由于子線程不是在一個(gè)獨(dú)立的環(huán)境執(zhí)行的,所以某個(gè)子線程掛了還是會(huì)影響到其它線程,在這種情況下,你需要做一些額外的措施來(lái)保護(hù)其余線程不受影響。

  • 線程數(shù)據(jù)共享實(shí)現(xiàn)麻煩:和其它后端語(yǔ)言比起來(lái),Node的數(shù)據(jù)共享還是比較麻煩的,不過(guò)這其實(shí)也避免了它需要考慮很多多線程下數(shù)據(jù)安全的問題。

到此,關(guān)于“Node處理CPU密集型任務(wù)的方法是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI