Hệ điều hành & Tiến trình/Tổng kết module — Thread & Lập lịch CPU
21/28
Bài 21 / 28~6 phútThread & Lập lịch CPUMiễn phí lượt xem

Tổng kết module — Thread & Lập lịch CPU

Cheat sheet thread vs process + trạng thái + scheduler, glossary, pitfall tổng hợp và self-assessment đối chiếu learning outcomes.

TL;DR: Module này trả lời câu hỏi "4 core chạy 400 tiến trình thế nào" bằng bốn mảnh ghép. Thread khác process ở chỗ chia sẻ address space (chung heap/code, riêng stack/register) — nên tạo và chuyển đổi rẻ hơn. Một thread luân phiên ready/running/blocked, và blocked thì nhường CPU chứ không phí. Mỗi lần đổi thread là một context switch tốn cả phần trực tiếp (register) lẫn gián tiếp (cache lạnh, TLB flush). Scheduler preemptive dùng timer interrupt chia core theo time slice, priority/nice và ý tưởng công bằng của CFS. Cuối cùng, phân loại workload CPU-bound vs I/O-bound cho bạn công thức chọn số thread. Đây là trang để bookmark.

Đã đi qua những gì

Module bắt đầu từ thread vs process: thread cùng process chia sẻ code, dữ liệu toàn cục, heap và bảng file descriptor, nhưng mỗi thread giữ riêng stack và bộ register (gồm program counter, stack pointer) — vì đó là thứ tối thiểu để một luồng chạy độc lập. Chia sẻ address space làm tạo thread rẻ hơn tạo process khoảng một bậc, và giao tiếp giữa thread gần như miễn phí — đổi lại phải đồng bộ khi ghi chung, và mất khả năng cách ly lỗi.

Rồi bạn đưa thread vào chuyển động: ba trạng thái ready (chờ tới lượt), running (trên core), blocked (chờ sự kiện). Ý quan trọng nhất — blocked không tốn CPU — là nền cho cả module: kernel lấy thread chờ khỏi core và chạy thread khác. Mỗi lần đổi thread là một context switch, và chi phí thật của nó không chỉ là lưu/khôi phục register (vài µs) mà còn là cái đuôi gián tiếp: thread mới vào gặp cache lạnh, TLB có thể vừa bị flush — nối thẳng về cache/TLB ở khoá Bộ nhớ. Đổi thread cùng process nhẹ hơn đổi process vì không đổi address space.

Tiếp theo là scheduler: lập lịch preemptive dùng timer interrupt để cướp CPU đều đặn, nên while (true) {} không treo được máy. Time slice là đánh đổi giữa phản hồi nhanh và chi phí switch; nice (-20..+19) điều chỉnh ưu tiên; và CFS chọn thread có vruntime nhỏ nhất qua cây đỏ-đen, tự nhiên ưu tiên thread hay ngủ (I/O-bound).

Cuối cùng, mọi thứ hội tụ ở CPU-bound vs I/O-bound: CPU-bound thì số thread ≈ số core (thêm nữa chỉ tăng switch), I/O-bound thì nhiều thread hơn core để lấp thời gian chờ. Mini-challenge cho bạn tự đo hai đường cong đó trên máy mình.

flowchart TD
  ROOT["Thread va Lap lich CPU"]
  ROOT --> TP["Thread vs process"]
  ROOT --> ST["Trang thai va context switch"]
  ROOT --> SC["Scheduler"]
  ROOT --> WL["CPU-bound vs I/O-bound"]
  TP --> TPd["Chia se: code, heap, data, fd<br/>Rieng: stack, register, PC, SP<br/>Tao thread re hon ~1 bac<br/>Process de cach ly, thread de chia se"]
  ST --> STd["Ready / running / blocked<br/>Blocked khong ton CPU<br/>Truc tiep: luu/nap register<br/>Gian tiep: cache lanh, TLB flush"]
  SC --> SCd["Preemptive: timer interrupt<br/>Time slice: phan hoi vs overhead<br/>Nice -20..+19: uu tien tuong doi<br/>CFS chon vruntime nho nhat"]
  WL --> WLd["CPU-bound: so thread ~ so core<br/>I/O-bound: nhieu thread hon core<br/>Cong thuc: core x (1 + cho/tinh)<br/>Do that roi dieu chinh"]

🗺️ Cheat sheet

Khái niệmCốt lõiPitfall / khi nào dùng
ProcessChương trình đang chạy, có address space riêngCách ly lỗi/bảo mật; tạo đắt hơn thread
ThreadLuồng thực thi trong một processChia sẻ heap → cần đồng bộ; một thread sập kéo cả process
Chia sẻ (thread)Code, dữ liệu toàn cục, heap, bảng file descriptorGhi chung không đồng bộ → race condition
Riêng (thread)Stack, bộ register (PC, SP), thread ID, errnoBiến cục bộ (stack) an toàn tự nhiên, không cần đồng bộ
ReadySẵn sàng chạy, chờ core rảnh
RunningĐang chiếm một coreChỉ trạng thái này tiêu CPU
BlockedChờ sự kiện (I/O, lock), không cần CPUBusy-wait (while quay vòng) thì vẫn đốt CPU — khác blocking
Context switchĐổi thread trên một core: lưu/nạp registerTrực tiếp vài µs; gián tiếp (cache/TLB) thường lớn hơn
Thread switch vs process switchCùng process không flush TLB; khác process thì cóPCID/ASID giảm nhẹ đòn TLB nhưng không xoá
Preemptive schedulingTimer interrupt cướp CPU đều đặnwhile (true) {} không treo máy được
Time sliceThời gian chạy trước khi preemptNgắn = phản hồi nhanh + tốn switch; dài = ngược lại
Nice value-20 (ưu tiên cao) tới +19 (thấp); ~1,25× mỗi đơn vịKhông đặt mức CPU tuyệt đối; giới hạn cứng dùng cgroups
CFSChọn thread có vruntime nhỏ nhất (cây đỏ-đen)Cân theo tổng thời gian chạy, không phải số lượt
vruntimeThời gian ảo mỗi thread đã chạy (điều chỉnh theo nice)Thread hay ngủ tích ít → được ưu tiên khi thức
CPU-boundGần như luôn tính, nút cổ chai là coreSố thread ≈ số core; thêm nữa gây switch, chậm hơn
I/O-boundPhần lớn thời gian blocked chờNhiều thread hơn core; coi chừng nút cổ chai downstream
Công thức số threadsố_core × (1 + chờ/tính)Heuristic — đo thật rồi điều chỉnh

📖 Glossary module

Thuật ngữĐịnh nghĩa 1 câu
ThreadLuồng thực thi bên trong một process; một process có ít nhất một thread
ProcessMột chương trình đang chạy, sở hữu một không gian địa chỉ ảo riêng
Address spaceKhông gian địa chỉ ảo một process thấy; thread cùng process chia sẻ nó
Stack (thread)Vùng lưu biến cục bộ và khung lời gọi hàm, riêng mỗi thread
Program counterRegister chỉ địa chỉ lệnh kế tiếp sẽ chạy — riêng mỗi thread
Context switchThao tác kernel đổi thread đang chạy trên một core, lưu/nạp trạng thái CPU
TLBBộ đệm ánh xạ địa chỉ ảo→vật lý; có thể bị flush khi đổi process
Ready stateThread sẵn sàng chạy nhưng đang chờ được cấp một core
Running stateThread đang thực thi trên một core
Blocked stateThread đang chờ một sự kiện (I/O, lock), tạm thời không cần CPU
Busy-waitVòng lặp chủ động kiểm tra điều kiện — đốt CPU, khác với blocking
SchedulerPhần kernel chọn thread ready nào chạy tiếp trên mỗi core
Preemptive schedulingKernel có thể lấy lại CPU khỏi thread bất cứ lúc nào (qua timer interrupt)
Timer interruptInterrupt phần cứng phát đều đặn, đưa kernel vào để preempt
Time slice (quantum)Lượng thời gian một thread được chạy trước khi bị cân nhắc preempt
Nice valueSố (-20..+19) điều chỉnh mức ưu ái của thread với scheduler
CFSCompletely Fair Scheduler — scheduler Linux chọn thread vruntime nhỏ nhất
vruntimeThời gian ảo tích luỹ mỗi thread đã chạy, điều chỉnh theo nice
CPU-boundWorkload bị giới hạn bởi tốc độ CPU; thread gần như luôn running
I/O-boundWorkload bị giới hạn bởi I/O; thread phần lớn thời gian blocked
Thread poolTập thread tái dùng để chạy nhiều tác vụ, tránh tạo/huỷ thread liên tục

⚠️ Pitfall tổng hợp

1. Tưởng mỗi thread có heap riêng:

// SAI (hieu nham): tuong A va B co ban list rieng
sharedList.add("x");        // A
int n = sharedList.size();  // B thay ngay -- CHUNG HEAP

// DUNG: chi 1 heap; sharedList la 1 object duy nhat.
// Ghi chung phai dong bo (module 04). Bien cuc bo (stack) moi rieng.

2. Busy-wait thay vì blocking:

// SAI: dot 100% mot core de "cho"
while (!dataReady) { }

// DUNG: nhuong core cho toi khi co su kien
synchronized (lock) {
    while (!dataReady) lock.wait();   // thread -> BLOCKED, khong ton CPU
}

3. Tưởng nice giới hạn CPU tuyệt đối:

# SAI (hieu nham): tuong nice +19 = toi da X% CPU
nice -n 19 ./job.sh    # van dung 100% mot core neu khong ai khac can

# DUNG: gioi han cung bang cgroups CPU quota, khong phai nice

4. Thêm thread cho workload CPU-bound:

// SAI: 200 thread cho tac vu tinh toan tren 4 core
// -> van 4 core lam viec, + hang loat context switch -> CHAM HON
ExecutorService pool = Executors.newFixedThreadPool(200);

// DUNG: CPU-bound -> so thread ~ so core
int cores = Runtime.getRuntime().availableProcessors();
ExecutorService pool2 = Executors.newFixedThreadPool(cores);

5. Gộp CPU-bound và I/O-bound vào một pool:

// SAI: mot con so khong toi uu cho hai ban chat trai nguoc
ExecutorService pool = Executors.newFixedThreadPool(50);

// DUNG: tach pool, chinh so thread doc lap theo loai
ExecutorService cpuPool = Executors.newFixedThreadPool(cores);       // tinh nang
ExecutorService ioPool  = Executors.newFixedThreadPool(cores * 5);   // goi mang/DB

✅ Self-assessment

Bạn đã đạt module này nếu trả lời được:

  • Compare được thread và process: cái gì chia sẻ (code, heap, dữ liệu toàn cục, bảng file descriptor), cái gì riêng (stack, register gồm PC/SP), và khi nào chọn process (cách ly) vs thread (chia sẻ rẻ).
  • Trace được vòng đời một thread qua ba trạng thái ready/running/blocked và chi phí thật của mỗi lần context switch — cả trực tiếp (lưu/nạp register) lẫn gián tiếp (cache lạnh, TLB flush), và vì sao đổi thread cùng process rẻ hơn đổi process.
  • Explain được scheduler preemptive chia CPU thế nào: timer interrupt cướp CPU, đánh đổi độ dài time slice, nice value, và ý tưởng CFS chọn vruntime nhỏ nhất.
  • Choose được số thread hợp lý cho workload CPU-bound (≈ số core) vs I/O-bound (nhiều hơn, theo tỉ lệ chờ/tính) dựa trên dự đoán bản chất workload — và giải thích vì sao mỗi loại như vậy.

🚀 What's next

Module này cho bạn hiểu một thread/tiến trình chạy và được chia CPU thế nào. Nhưng khi nhiều thread cùng ghi một dữ liệu chung — chính cái heap chia sẻ ở bài 01 — mọi thứ có thể vỡ theo cách khó lường: hai thread cùng count++ mất kết quả của nhau, hai thread cùng chờ nhau nhả lock thì treo mãi mãi.

Tiếp theo — Module 04: Đồng bộ và phối hợp. Bạn sẽ học vì sao count++ không nguyên tử, race condition xảy ra thế nào, mutex và atomic sửa nó ra sao, deadlock hình thành và cách tránh, và các cơ chế IPC (pipe, shared memory, socket) để process trao đổi. Đây là nửa còn lại của bức tranh đa nhiệm: module 03 dạy chia CPU, module 04 dạy phối hợp an toàn khi chia sẻ dữ liệu.

Bắt đầu: Module 04 — Tổng quan: Đồng bộ và phối hợp

Khám phá thêm: Tất cả khoá học

📚 Tài liệu mở rộng

  • OSTEP — "Operating Systems: Three Easy Pieces" (Arpaci-Dusseau), miễn phí tại https://pages.cs.wisc.edu/~remzi/OSTEP/ — các chương "The Abstraction: The Process", "Scheduling: Introduction" và "Multiprocessor Scheduling" phủ đúng module này, sâu hơn và có ví dụ đo đạc.
  • pthreads(7)clone(2) — man page chuẩn về thread POSIX và cơ chế chia sẻ trên Linux; đọc để thấy thread và process chỉ là "task" với mức chia sẻ khác nhau.
  • sched(7)Tài liệu kernel — CFS Scheduler — nguồn chuẩn về chính sách lập lịch, nice, và thiết kế CFS (kèm ghi chú CFS nhường chỗ EEVDF).
  • "Java Concurrency in Practice" (Brian Goetz, 2006) — chương 8 "Applying Thread Pools" cho công thức chọn số thread và cách kích cỡ pool theo workload; nền để áp bài 04/05 vào code Java thật.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên