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:
Node.js Event Loop: Hiểu Sâu Về Non-blocking I/O
#performance#nodejs#event-loop#async

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

Bình luận(0)

Đăng nhập để tham gia thảo luận