Node.js Event Loop: Hiểu Sâu Về Non-blocking I/O
Event Loop là trái tim của Node.js. Bài này giải thích chi tiết cách nó hoạt động và tại sao Node.js lại hiệu quả với I/O.
Minh Dev28 tháng 11 202410 phút4 thẻ
Share:
#performance#nodejs#event-loop#async
Nội dung chính
Node.js là Single-threaded — Nhưng Không Phải Vậy Hoàn Toàn
Node.js chạy JavaScript trên một thread duy nhất — nhưng nó sử dụng libuv để xử lý I/O operations bất đồng bộ thông qua một thread pool (mặc định 4 threads).
┌─────────────────────────────────┐
│ JavaScript │
│ (V8 Engine) │
│ Single Thread │
└─────────────┬───────────────────┘
│
┌─────────────▼───────────────────┐
│ Event Loop │
│ (libuv - C++ library) │
└─────┬──────┬──────┬─────────────┘
│ │ │
Timers I/O Thread Pool
(Phase 1)(Phase 2) (4 threads)
Các Phase của Event Loop
Event Loop chạy qua các phases theo thứ tự:
┌──────────────────────────┐
┌─►│ timers │ ← setTimeout, setInterval callbacks
│ └──────────────────────────┘
│ ┌──────────────────────────┐
│ │ pending callbacks │ ← I/O errors từ vòng trước
│ └──────────────────────────┘
│ ┌──────────────────────────┐
│ │ idle, prepare │ ← Internal use only
│ └──────────────────────────┘
│ ┌──────────────────────────┐
│ │ poll │ ← Fetch new I/O events
│ └──────────────────────────┘
│ ┌──────────────────────────┐
│ │ check │ ← setImmediate callbacks
│ └──────────────────────────┘
│ ┌──────────────────────────┐
└──│ close callbacks │ ← socket.on('close', ...)
└──────────────────────────┘
Thứ Tự Thực Thi
javascript
console.log('1: Start')
setTimeout(() => console.log('2: setTimeout'), 0)
Promise.resolve().then(() => console.log('3: Promise'))
process.nextTick(() => console.log('4: nextTick'))
console.log('5: End')
// Output:
// 1: Start
// 5: End
// 4: nextTick ← process.nextTick chạy trước mọi thứ khác
// 3: Promise ← Microtask queue
// 2: setTimeout ← Macro task (timers phase)setTimeout vs setImmediate
javascript
// Trong global scope: thứ tự không xác định
setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate'))
// Trong I/O callback: setImmediate luôn chạy trước
const fs = require('fs')
fs.readFile('file.txt', () => {
setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate')) // luôn in trước
})Blocking vs Non-blocking
javascript
// BLOCKING — Đừng làm thế này!
const data = fs.readFileSync('large-file.txt')
// Node.js KHÔNG THỂ xử lý bất kỳ request nào khác trong lúc này
// NON-BLOCKING — Đúng cách
fs.readFile('large-file.txt', (err, data) => {
if (err) throw err
processData(data)
})
// Node.js tiếp tục xử lý các requests khác trong khi đọc file
// Với async/await (cleaner syntax)
async function readAndProcess() {
try {
const data = await fs.promises.readFile('large-file.txt')
processData(data)
} catch (err) {
console.error(err)
}
}Worker Threads — Khi Cần CPU-intensive Tasks
javascript
const { Worker, isMainThread, parentPort } = require('worker_threads')
if (isMainThread) {
// Main thread
const worker = new Worker(__filename)
worker.on('message', (result) => {
console.log('Result:', result) // 499999500000
})
worker.postMessage(1000000)
} else {
// Worker thread
parentPort.once('message', (n) => {
let sum = 0
for (let i = 0; i < n; i++) sum += i
parentPort.postMessage(sum)
})
}Kết Luận
Event Loop là lý do Node.js xử lý hàng nghìn concurrent connections mà không cần multi-threading:
- I/O-bound tasks: Node.js cực kỳ hiệu quả
- CPU-bound tasks: Cần Worker Threads hoặc child processes
- Luôn hiểu thứ tự: nextTick → Microtasks → Macrotasks
0Lượt xem