# The Internals of Node
### Node 如何被執行?
以執行 pbkdf2 為例,我們會經歷以下六個歷程。
1. **我們撰寫的 JavaScript**
```javascript
app.get('/', (req, res) => {
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
res.send('Hello World')
})
})
```
2. **lib 的原始碼** (JavaScript 函式庫)
```javascript
// lib/internal/crypto/pbkdf2.js
const { validateCallback } = require('internal/validators')
function pbkdf2(password, salt, iterations, keylen, digest, callback) {
// 檢驗參數是否符合格式
if (typeof digest === 'function') {
callback = digest
digest = undefined
}
;({ password, salt, iterations, keylen, digest } = check(
password,
salt,
iterations,
keylen,
digest
))
validateCallback(callback)
// 透過 internalBinding 呼叫 C++ 函式庫
// ...
}
```
3. **internalBinding** (連接 JavaScript 和 C++ 的橋樑)
```javascript
// lib/internal/crypto/pbkdf2.js
const { PBKDF2Job } = internalBinding('crypto')
function pbkdf2(password, salt, iterations, keylen, digest, callback) {
// 檢驗參數是否符合格式
// ...
// 透過 internalBinding 呼叫 C++ 函式庫
const job = new PBKDF2Job(
kCryptoJobAsync,
password,
salt,
iterations,
keylen,
digest
)
const encoding = getDefaultEncoding()
job.ondone = (err, result) => {
if (err !== undefined) return FunctionPrototypeCall(callback, job, err)
const buf = Buffer.from(result)
if (encoding === 'buffer')
return FunctionPrototypeCall(callback, job, null, buf)
FunctionPrototypeCall(callback, job, null, buf.toString(encoding))
}
job.run()
}
```
4. **V8** (轉換 JavaScript 和 C++ 間的變數)
5. **src 的原始碼** (C++ 函式庫)
6. **libuv** (提供 Node 接觸底層 OS 的權限)
### Node Event Loop
```javascript
const pendingTimers = []
const pendingOSTasks = []
const pendingOperations = []
// New timers, tasks, operations 會在 myFile 被執行時被記錄下來
myFile.runContents()
function shouldContinue() {
/*
* 檢查1: 任何待處理的 setTimeout, setInterval, setImmediate?
* 檢查2: 任何待處理的 OS tasks? (Like server listening to port)
* 檢查3: 任何待處理的 long running operations? (Like fs module)
* */
return (
pendingTimers.length || pendingOSTasks.length || pendingOperations.length
)
}
// 整段程式在一個'tick'被執行的情境
while (shouldContinue()) {
// 1) Node 看向 pendingTimers 並檢查是否有函式準備好被執行 (setTimeout, setInterval)
// 2) Node looks at pendingOSTasks and pendingOperations and calls relevant callbacks
// 3) 暫停執行,只在有以下情況下繼續執行...
// - 一個新的 pendingOSTask 完成
// - 一個新的 pendingOperation 完成
// - 一個 timer 準備要完成
// 4) 看向 pendingTimers. 呼叫任何 setImmediate
// 5) 處理任何 'close' 事件
}
// 退出返回 terminal
```
### Node 是單線程嗎?
Node **Event Loop** 是單線程。
**有一些** Node **框架/Std Lib 不是**單線程。
### Libuv Thread Pool
```javascript
// 調整 libuv thread pool 大小
process.env.UV_THREADPOOL_SIZE = 2
const crypto = require('crypto')
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('1:', Date.now() - start)
})
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('2:', Date.now() - start)
})
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('3:', Date.now() - start)
})
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('4:', Date.now() - start)
})
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log('5:', Date.now() - start)
})
```
結果可能會隨你電腦 CPU 的核心數、 **libuv thread pool** 的大小而影響,不過重要的是 Node Event Loop 指派任務是單線程,但是底層的 C++ 被執行則可能是多線程。
以下提供 task 的行進路線幫助理解底層的運作機制:
Task → Thread Pool → OS Task Scheduler → CPU Core
###### tags: `Node JS: Advanced Concepts` `NodeJS` `JavaScript`