Khi lần đầu nghe "Node.js chạy single-threaded", nhiều người thắc mắc: vậy làm sao nó handle được 10.000 requests cùng lúc? Câu trả lời nằm ở Event Loop — một trong những thiết kế thông minh nhất trong software engineering.
Mô Hình Truyền Thống Vs Node.js
Server truyền thống (Apache, Django với WSGI):
- Mỗi request → một thread riêng
- Thread chờ I/O (DB, file) → bị block
- 1000 concurrent requests → 1000 threads → tốn RAM
Node.js:
- Một thread, non-blocking I/O
- Khi chờ I/O → event loop làm việc khác
- 1000 concurrent requests → vẫn một thread
Event Loop — Cơ Chế Hoạt Động
// Mỗi "tick" của Event Loop:
// 1. Xử lý hết callback trong call stack
// 2. Microtasks (Promise, queueMicrotask)
// 3. Macrotasks (setTimeout, setInterval, I/O callbacks)
console.log('1'); // Sync — chạy ngay
setTimeout(() => console.log('2'), 0); // Macrotask — chạy sau
Promise.resolve().then(() => console.log('3')); // Microtask — chạy trước macrotask
console.log('4'); // Sync — chạy ngay
// Output: 1, 4, 3, 2
Tại Sao Non-blocking?
const fs = require('fs');
// BLOCKING — block toàn bộ event loop trong khi đọc file!
const data = fs.readFileSync('large-file.txt');
// Trong lúc này, KHÔNG request nào khác được xử lý
// NON-BLOCKING — đăng ký callback và tiếp tục ngay
fs.readFile('large-file.txt', (err, data) => {
// Callback này chỉ chạy khi file đã đọc xong
console.log(data.length);
});
// Event loop tiếp tục xử lý requests khác trong khi chờ file
Phases Của Event Loop
// Event Loop chạy theo vòng, mỗi vòng có nhiều phases:
//
// timers → I/O callbacks → idle/prepare → poll → check → close callbacks
//
// poll phase: Chờ I/O events (file read, network) — đây là phần tốn thời gian nhất
// timers: Chạy setTimeout và setInterval đã đến hạn
// check: Chạy setImmediate callbacks
setImmediate(() => console.log('setImmediate'));
setTimeout(() => console.log('setTimeout 0'), 0);
// Thứ tự phụ thuộc vào phase hiện tại
Blocking The Event Loop — Kẻ Thù Số Một
// XẤU — CPU-intensive block event loop
app.get('/api/compute', (req, res) => {
// Tính toán nặng chạy synchronously
let result = 0;
for (let i = 0; i < 1e9; i++) result += i;
res.json({ result });
// Trong 5 giây này, KHÔNG request nào khác được xử lý!
});
// TỐT — đẩy vào Worker Thread
const { Worker } = require('worker_threads');
app.get('/api/compute', (req, res) => {
const worker = new Worker('./compute-worker.js');
worker.on('message', result => res.json({ result }));
});
Async/Await Và Event Loop
// async/await là syntactic sugar cho Promise
// Bên dưới vẫn dùng Event Loop
async function handleRequest(req, res) {
// Đây là non-blocking — event loop tiếp tục trong khi chờ DB
const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
// Parallel I/O — chạy cùng lúc, không sequential
const [posts, followers] = await Promise.all([
db.query('SELECT * FROM posts WHERE user_id = ?', [user.id]),
db.query('SELECT * FROM followers WHERE user_id = ?', [user.id]),
]);
res.json({ user, posts, followers });
}
Node.js không phải lúc nào cũng tốt hơn Python hay Java. Nó tỏa sáng với I/O-bound workloads (API gateway, real-time apps). Với CPU-bound (video encoding, ML), Go hoặc Python với multiprocessing phù hợp hơn.
Kết Luận
Hiểu Event Loop giúp bạn tránh được bug phổ biến nhất với Node.js: vô tình block event loop bằng synchronous operations. Quy tắc đơn giản: mọi I/O đều phải async, mọi CPU-intensive task phải ra Worker Thread.
Chưa có bình luận. Hãy là người đầu tiên!