你真的懂 Node.js 裡 Libuv 如何使用 Thread Pool 嗎?

不廢話先上題,請問以下程式 conosole.log 的順序?

const https = require('https');
const crypto = require('crypto');
const fs = require('fs');

const start = Date.now();

function doRequest() {
https
.request('https://www.google.com', res => {
res.on('data', () => {})
res.on('end', () => {
console.log('Request:', Date.now() - start);
})
})
.end();
}

function doHash() {
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('Hash:', Date.now() - start);
})
}


doRequest();

fs.readFile('text.txt', 'utf8', () => {
console.log('FS:', Date.now() - start);
})

doHash();
doHash();
doHash();
doHash();
  • 執行結果

image

為什麼會有此結果?

第一個點我需要了解到 node 底層使用了 Libuv 處理檔案讀寫和計算 hash 等功能,Libuv 用了Thread Pool 中的 Thread 來處理這些任務,Thread Pool 預設的 Thread 數量為 4 個,換句話說只有 4 個 Libuv 負責的任務可以同時進行,然後 https 請求不是由 Libuv 負責,而是 OS 負責,OS 會決定是否開一條新的 Thread 來處理此任務,因此我們就看到了 HTTPS 請求最快完成,但是問題來了,為什麼 fs module 檔案讀了這麼久?

那我們需要先了解讀檔案的流程

image

上圖我們可以看到, node 沒有直接發 request 去讀檔案,而是在第二步時,先取得該檔案的狀態資訊,才後才在第四步發 request 去讀檔案

我們來看執行的方式

image

一開始 fs, PBKDF2 1 ~ 3 號就將 Thread Pool 直接佔滿了,然後 Thread #1 發了 Request 去取得該檔案的狀態

image

但是,Thread #1 發現要等硬碟將檔案狀態返回不知道要等多久,所以等的期間就不繼續執行 FS 的任務了,就讓 FS 任務等一下吧

image

所以這時 Thread #1 手上沒有任務了,PBKDF2 #4 的任務就被分配給 Thread 1# 執行了

image

最終 Thread #2, #3, #4 其中一個 Thread 會結束工作,我們假設 Thread #2 做完 PBKDF2 #1,就把 FS 的任務拿過來繼續做了,我記得我們剛剛講到 node 讀取檔案的步驟嗎?Node 會先 Request 要檔案的資訊,再發 Request 讀取檔案的內容,所以理論上 Thread #2 也會遇到一個將 Request 發給硬碟後的空窗期,但後面已經沒有任務了所以它就空著繼續等

image

這就是為什麼我們會在執行給果看到先 Hash 先完成,FS 會在後一個完成

image

驗收:那如果我們將 Hash 減少到兩個,結果會是如何呢?

image

現在每個任務都有一個 Thread 去執行了,FS 反而變最快了

image

Node.js 效能提升 使用 apollo-studio 與 express-session 無法 set-cookie 問題
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×