3.1 Thread là gì?
Thread là đơn vị thực thi nhỏ hơn process. Một process có thể chứa nhiều thread, các thread chia sẻ memory của process. Vì thế thread thường được gọi là "lightweight process".
Khi process chỉ có 1 thread, đó gọi là single-threaded process. Khi có nhiều thread, multi-threaded process. Trên Linux, thread thực ra là một loại "process" đặc biệt cùng chia sẻ address space — gọi là LWP (Lightweight Process).
3.3 Thread vs Process — bảng phỏng vấn
| Tiêu chí | Process | Thread |
|---|---|---|
| Tạo (cost) | Đắt — kernel phải copy/setup PCB, page table | Rẻ — chỉ tạo stack mới |
| Memory | Riêng (cách ly hoàn toàn) | Chia sẻ heap, code, data |
| Context switch | Đắt — đổi page table, flush TLB | Rẻ — cùng address space |
| Communication (IPC) | Phải qua pipe/socket/shared mem | Đọc/ghi biến chia sẻ trực tiếp |
| Crash isolation | 1 process crash không ảnh hưởng process khác | 1 thread crash → cả process chết |
| Sync overhead | Ít cần (đã cô lập) | Nhiều — race condition phải lock |
| Debugging | Dễ — state riêng | Khó — race, deadlock |
Khi nào nên dùng cái gì?
- Multi-process: khi cần cô lập (vd Chrome tab — crash 1 tab không sập browser); khi tính song song không cần chia sẻ data; khi muốn restart từng phần.
- Multi-thread: khi cần chia sẻ data nhiều (vd web server xử lý nhiều request cùng struct lớn); khi tạo/destroy thường xuyên.
Ngày nay, cluster + thread pool (Node.js cluster + worker_threads) hoặc process pool (Python multiprocessing) là pattern phổ biến: cluster cho cô lập, thread cho song song trong mỗi cluster.
3.4 User-level vs Kernel-level Thread
Kernel-level Thread
- OS biết về thread, scheduler quản lý từng thread
- Tạo bằng syscall (
clonetrên Linux) - Mỗi thread có TID unique trên hệ thống
- Block 1 thread không block thread khác
- Có thể tận dụng nhiều CPU core
User-level Thread
- OS chỉ thấy 1 thread; scheduler ở user space (thư viện) tự manage
- Tạo cực nhanh — không cần syscall
- Block 1 user thread = block toàn bộ process (vì OS thấy 1 thread)
- Không tự động tận dụng multi-core
- Vd: green thread ở Erlang, fibers, coroutines (cũ)
OS hiện đại (Linux, macOS, Windows) dùng kernel-level thread cho thread API tiêu chuẩn (pthread).
3.5 Threading Models — M:1, 1:1, M:N
3 mô hình ánh xạ user thread → kernel thread:
3.5.1 Many-to-One (M:1)
Nhiều user thread map về một kernel thread.
- Tạo thread cực rẻ (toàn user space)
- Nhược: 1 thread block I/O → tất cả block; không multi-core
- Ngày nay ít dùng
3.5.2 One-to-One (1:1)
Mỗi user thread map về một kernel thread.
- Concurrency tốt, multi-core OK
- Nhược: tạo thread đắt (mỗi thread cần kernel resource)
- Linux pthreads, Windows threads, Java thread đều 1:1
- Đây là model phổ biến nhất hiện nay
3.5.3 Many-to-Many (M:N)
M user thread map về N kernel thread (M ≥ N).
- Linh hoạt: tạo nhiều user thread, OS chỉ schedule N kernel thread
- Phức tạp implement
- Vd: Go goroutines dùng M:N (M goroutine, N OS thread). Đây là lý do Go cho phép tạo hàng triệu goroutine.
3.6 Thread Pool — pattern thực tế nhất
Tạo thread không miễn phí. Nếu mỗi request HTTP tạo 1 thread mới rồi destroy, server đông user sẽ chết. Thread Pool giải quyết: tạo sẵn N thread, request đến → đẩy vào queue, thread nhàn rỗi nhặt việc.
Lợi ích
- Tái sử dụng thread → không tốn create/destroy
- Giới hạn concurrency → không quá tải máy
- Predictable resource usage
Ai dùng thread pool?
- Java: ExecutorService, ThreadPoolExecutor
- Tomcat: mỗi HTTP request → thread pool
- Database connection pool: cùng concept với thread pool
- Node.js libuv: default 4 thread cho file I/O blocking
- nginx: dùng worker process pool, mỗi worker là event-loop
Quy tắc kinh nghiệm: kích thước pool = số CPU core × (1 + waitTime/computeTime). Web server I/O-heavy: 2-4× cores. CPU-heavy: 1× cores.
3.7 Node.js Event Loop — single-threaded mà vẫn nhanh
Câu hỏi phỏng vấn JavaScript backend cực kinh điển: "Node.js single-threaded, làm sao xử lý hàng nghìn request đồng thời?"
Câu trả lời nằm ở Event Loop + libuv.
Cách Node xử lý 1000 request đồng thời
const http = require('http');
http.createServer((req, res) => {
// Mỗi request KHÔNG tạo thread mới
// Code chạy trên main thread JS
fs.readFile('/big.json', (err, data) => {
res.end(data);
});
}).listen(3000);
Khi 1000 request đến cùng lúc:
- Main thread nhận từng request, gọi
fs.readFile - libuv đẩy 4 lệnh đọc file vào thread pool (default 4 workers); 996 còn lại vào queue
- Main thread không đợi — quay lại nhận request khác
- Khi worker đọc xong file, libuv push callback vào event loop queue
- Main thread chạy callback, gửi response, tiếp tục
6 phase của Event Loop
┌───────────────────────┐
┌─▶│ timers │ ← setTimeout, setInterval
│ └──────────┬────────────┘
│ ┌──────────▼────────────┐
│ │ pending callbacks │ ← syscall errors
│ └──────────┬────────────┘
│ ┌──────────▼────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘
│ ┌──────────▼────────────┐
│ │ poll │ ← I/O callbacks (đa số việc ở đây)
│ └──────────┬────────────┘
│ ┌──────────▼────────────┐
│ │ check │ ← setImmediate
│ └──────────┬────────────┘
│ ┌──────────▼────────────┐
└──│ close callbacks │ ← socket.on('close')
└───────────────────────┘
process.nextTick & Promise microtasks chạy giữa mỗi phase, ưu tiên cao nhất.
3.8 Worker Threads — Node có thread thật
Từ Node 10.5+, có module worker_threads: tạo thread thật (pthread) để chạy CPU-bound work
song song với main thread.
// main.js
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.on('message', (result) => {
console.log('Result:', result);
});
worker.postMessage({ n: 40 });
} else {
// Code chạy trong worker thread
parentPort.on('message', ({ n }) => {
// CPU-bound work — fib(40) khá chậm
function fib(x) { return x < 2 ? x : fib(x-1) + fib(x-2); }
parentPort.postMessage(fib(n));
});
}
Worker Threads vs Cluster vs child_process
| API | Bản chất | Memory | Khi nào dùng |
|---|---|---|---|
| worker_threads | Thread thật trong cùng process | Chia sẻ ArrayBuffer | CPU-bound work, chia sẻ data lớn |
| cluster | Fork process, share port HTTP | Riêng | Scale HTTP server qua nhiều core |
| child_process.fork | Process Node mới | Riêng | Chạy script Node độc lập, IPC |
| child_process.spawn | Process bất kỳ | Riêng | Chạy command shell |
3.9 Concurrency vs Parallelism — phải phân biệt
Concurrency
Nhiều task đan xen trên ít CPU. Ảo giác chạy "cùng lúc" nhưng thật ra là chuyển qua chuyển lại nhanh.
Ví dụ: Node.js single thread xử lý 1000 request bằng event loop.
Câu hỏi giải quyết: "Cấu trúc app như nào để xử lý nhiều task không bị block?"
Parallelism
Nhiều task thật sự chạy cùng lúc trên nhiều CPU core.
Ví dụ: 4 thread tính toán trên 4 core khác nhau.
Câu hỏi giải quyết: "Làm sao chia 1 task lớn thành nhiều phần để giảm thời gian?"
Quote nổi tiếng của Rob Pike: "Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once."
Vẽ ra cho rõ
Một system có thể vừa concurrent vừa parallel: vd Node.js cluster — concurrency trong từng process (event loop), parallelism giữa các process trên các core.
Bài tập
Trên Linux, lệnh nào liệt kê thread của 1 PID? ps -eLf | grep PID hoặc ls /proc/PID/task/.
Viết Node script với setTimeout(() => console.log('A'), 0) và một loop nặng (vd for (i < 1e9) {}) ngay sau.
Quan sát "A" được in khi nào. Giải thích.
Cài đặt fib(45) trong worker thread. So sánh CPU usage với main thread.
Mặc định Node libuv có 4 thread cho I/O. Đặt env UV_THREADPOOL_SIZE=8. Khi nào nên tăng?
Viết 1 web server đơn giản dùng cluster (4 worker). So sánh với 1 process dùng worker_threads. Khi nào nên dùng cái nào?
Cho 4 task: tải 4 image từ network và xử lý mỗi image. Trên 4-core CPU. Bạn cần concurrency, parallelism, hay cả hai? Tại sao?
🧪 Quiz cuối chương
Câu 1. Hai thread trong cùng process chia sẻ gì?
Đáp án: heap, code, data, fd. Mỗi thread có stack riêng và registers riêng. Heap chia sẻ → race condition.
Câu 2. Tạo thread nhanh hơn tạo process vì?
Đáp án: dùng chung address space. fork process phải copy page table, set up new PCB. Thread chỉ cần stack mới.
Câu 3. Node.js xử lý 1000 request đồng thời nhờ?
Đáp án: Event Loop + libuv. JS chạy single-threaded, nhưng I/O được delegate. Main thread chỉ chạy JS code khi có callback ready.
Câu 4. Concurrency vs Parallelism khác nhau ở đâu?
Đáp án: đan xen vs song song thật. Concurrency là về structure, parallelism là về execution.
Câu 5. Linux dùng threading model nào cho pthreads?
Đáp án: 1:1. Linux NPTL (Native POSIX Thread Library) dùng 1:1. Go dùng M:N với goroutine.
Câu 6. Khi 1 thread crash, chuyện gì xảy ra với các thread khác trong process?
Đáp án: cả process crash. Vì memory chia sẻ, 1 thread truy cập invalid memory → segfault → kernel kill cả process. Đó là lý do Chrome dùng multi-process thay vì multi-thread cho tab.
Câu 7. CPU-bound work trong Node nên chạy ở đâu?
Đáp án: Worker Threads. Main thread chạy CPU-bound sẽ block event loop → mọi request khác phải đợi.
Câu 8. Thread Pool có lợi gì?
Đáp án: tái sử dụng + giới hạn. Tránh tạo thread mỗi request, tránh quá tải khi traffic cao.
Tổng kết chương 3
- ✅ Thread = lightweight process; chia sẻ heap/code/data/fd, riêng stack/registers/TLS
- ✅ Process & Thread đánh đổi: cô lập vs hiệu năng; an toàn vs đồng bộ phức tạp
- ✅ Threading models: 1:1 (Linux pthreads), M:1 (cũ), M:N (Go goroutines)
- ✅ Thread Pool = pattern tái sử dụng thread, giới hạn concurrency
- ✅ Node.js Event Loop: single-threaded JS + libuv thread pool cho I/O — xử lý concurrent dù 1 thread
- ✅ Worker Threads: thread thật trong Node cho CPU-bound work
- ✅ Concurrency (đan xen) ≠ Parallelism (song song thật)
- ✅ Crash 1 thread = crash cả process (chia sẻ address space)