Hệ điều hành & Tiến trình/Scheduler CPU — timer interrupt, time slice & CFS
18/28
Bài 18 / 28~13 phútThread & Lập lịch CPUMiễn phí lượt xem

Scheduler CPU — timer interrupt, time slice & CFS

Preemptive scheduling: timer interrupt, time slice, priority, và ý tưởng CFS của Linux — vì sao máy 4 core chạy được 400 tiến trình mà bạn không nhận ra.

TL;DR: Scheduler là phần của kernel quyết định thread nào trong số các thread ready được chạy tiếp trên mỗi core. Linux (và mọi OS hiện đại) dùng lập lịch preemptive: một timer interrupt đều đặn (thường 100–1000 lần/giây) cướp CPU khỏi thread đang chạy, để kernel có cơ hội chọn lại — nhờ vậy một vòng lặp vô hạn không thể chiếm CPU mãi mãi. Mỗi thread được cấp một phần thời gian (time slice); hết slice thì bị preempt về ready. Độ dài slice là một đánh đổi: quá ngắn thì tốn context switch, quá dài thì phản hồi giật. Priority/nice (từ -20 tới +19 trên Linux) cho một số thread được ưu ái hơn. Scheduler mặc định của Linux là CFS: thay vì gán slice cứng, nó theo dõi mỗi thread đã chạy bao lâu (vruntime) và luôn chọn thread chạy ít nhất — công bằng, và tự nhiên ưu tiên thread hay ngủ (I/O-bound).

Máy bạn có 4 core. htop báo 412 tiến trình. Nếu mỗi core chạy đúng một thread cho tới khi nó xong, thì 408 tiến trình còn lại phải xếp hàng chờ — con trỏ chuột đơ, nhạc dừng, terminal không gõ được. Nhưng thực tế mọi thứ mượt: chuột mượt, nhạc chạy, build vẫn tiến. Ai đó đang chia nhỏ 4 core ra cho 412 tiến trình nhanh tới mức bạn không nhận ra sự chia sẻ. Người đó là scheduler.

Và có một câu hỏi cắc cớ: nếu một tiến trình chạy while (true) {} — vòng lặp không bao giờ gọi I/O, không bao giờ tự nhường CPU — thì làm sao các tiến trình khác được chạy? Nếu scheduler chỉ hoạt động khi thread tự nguyện nhường (cooperative), một vòng lặp tham lam sẽ đóng băng cả máy. Câu trả lời — timer interrupt — là trái tim của lập lịch hiện đại.

Bài này giải thích scheduler preemptive chia CPU thế nào: timer interrupt cướp CPU ra sao, time slice là gì và đánh đổi độ dài của nó, priority/nice, và ý tưởng cốt lõi của CFS. Không đi sâu vào code kernel — mục tiêu là bạn hình dung được cơ chế để đọc nice, htop, và hiểu bài 04 (chọn số thread).

1. Analogy — cô giáo gọi học sinh phát biểu

Một lớp 40 học sinh (thread ready) đều giơ tay, nhưng chỉ có một micro (một CPU core). Cô giáo (scheduler) phải quyết mỗi lúc trao micro cho ai.

Nếu cô để một học sinh nói cho tới khi nào bạn ấy tự dừng (cooperative), một bạn nói dài dòng sẽ chiếm hết tiết học — cả lớp mất lượt. Nên cô đặt đồng hồ: mỗi bạn nói tối đa 1 phút (time slice), hết giờ đồng hồ reo (timer interrupt), cô thu micro và trao cho bạn khác. Không ai độc chiếm được.

Cô cũng không hoàn toàn cào bằng: bạn nào ít được nói nhất từ đầu tiết thì cô ưu tiên gọi (để công bằng theo tổng thời gian nói, chứ không phải theo lượt). Và có bạn được cô thiên vị hơn một chút (priority/nice) — ví dụ bạn phụ trách trình bày chính.

Chi tiết tinh tế: bạn nào giơ tay hỏi một câu ngắn rồi ngồi xuống chờ (hay block chờ I/O) thì tổng thời gian nói của bạn ấy rất ít — nên theo quy tắc "ai nói ít nhất được gọi trước", những bạn này luôn được đáp ứng nhanh khi giơ tay. Đó chính là cách CFS tự nhiên ưu tiên thread I/O-bound mà không cần luật riêng.

Đời thườngKhái niệm
Cô giáo trao microScheduler chọn thread
Một cái microMột CPU core
40 học sinh giơ tayCác thread ready
Đồng hồ 1 phút mỗi lượtTime slice
Đồng hồ reo, cô thu microTimer interrupt → preempt
Ưu tiên bạn ít được nói nhấtCFS chọn vruntime nhỏ nhất
Thiên vị bạn thuyết trình chínhPriority / nice thấp
💡 Cách nhớ

Preemptive = có đồng hồ reo cướp lượt. Không có nó, một thread tham lam (while (true) {}) sẽ chiếm core mãi. Timer interrupt là cái biến "nhường tự nguyện" thành "bị cướp có kỷ luật".

2. Preemption — timer interrupt cướp CPU thế nào

Có hai kiểu chia CPU:

  • Cooperative (hợp tác): thread chỉ mất CPU khi nó tự nguyện nhường — gọi I/O, hoặc gọi một hàm yield(). Nếu một thread không bao giờ nhường (while (true) {}), nó chiếm core vĩnh viễn. Các OS đời đầu (Windows 3.x, Mac OS cổ) dùng kiểu này và một app treo là đóng băng cả máy.
  • Preemptive (ưu tiên/cướp): kernel có thể lấy lại CPU khỏi thread bất cứ lúc nào, kể cả khi thread không muốn nhường. Mọi OS hiện đại (Linux, Windows, macOS) dùng kiểu này.

Cơ chế biến preemptive thành hiện thực là timer interrupt. Phần cứng có một bộ đếm giờ được lập trình để phát interrupt đều đặn — trên Linux, tần số này (HZ) thường là 100, 250, hoặc 1000 lần/giây tuỳ cấu hình kernel. Mỗi lần timer interrupt xảy ra:

  1. CPU đang chạy thread người dùng thì bị ngắt, nhảy vào kernel để xử lý interrupt (interrupt là chủ đề của module 01).
  2. Kernel cập nhật số liệu thời gian đã chạy của thread hiện tại và hỏi scheduler: "thread này nên tiếp tục, hay đã đến lúc nhường?".
  3. Nếu đến lúc nhường (hết time slice, hoặc có thread ready xứng đáng hơn), kernel thực hiện context switch (bài 02) sang thread khác.
  4. Nếu chưa, thread hiện tại chạy tiếp — chỉ mất một chút thời gian cho lần ngắt.

Nhờ interrupt này, dù thread của bạn là while (true) {} không bao giờ tự nhường, timer vẫn đều đặn kéo kernel vào và cho thread khác cơ hội. Đó là vì sao một vòng lặp vô hạn trong một tiến trình không làm treo cả máy trên OS hiện đại — nó chỉ ngốn phần CPU của mình, còn con trỏ chuột và các tiến trình khác vẫn được chia lượt.

flowchart LR
  A["Thread dang chay<br/>(user mode)"] -->|timer interrupt| B["Kernel: cap nhat thoi gian<br/>hoi scheduler"]
  B -->|"con slice"| A
  B -->|"het slice / co thread xung dang hon"| C["Context switch<br/>sang thread khac"]

3. Time slice — dài bao nhiêu là vừa?

Time slice (còn gọi là quantum) là lượng thời gian một thread được chạy trước khi scheduler cân nhắc preempt nó. Chọn độ dài slice là một đánh đổi kinh điển, và OSTEP trình bày rất rõ:

  • Slice ngắn (ví dụ 1 ms): mỗi thread chờ ít hơn tới lượt mình → phản hồi nhanh (gõ phím, di chuột thấy mượt). Nhưng đổi thread thường xuyên hơn → nhiều context switch hơn, mà mỗi switch tốn chi phí trực tiếp + làm nguội cache/TLB (bài 02). Nếu slice ngắn tới mức xấp xỉ chi phí một context switch, CPU dành phần lớn thời gian đổi việc thay vì làm việc.
  • Slice dài (ví dụ 100 ms): ít context switch → ít lãng phí, thông lượng cao cho tác vụ tính toán. Nhưng một thread phải chờ lâu hơn tới lượt → phản hồi giật: bạn di chuột mà thấy khựng vì một thread khác đang giữ core cả 100 ms.
Time slice ngan  ---> phan hoi nhanh, NHUNG nhieu context switch (ton)
Time slice dai   ---> it overhead, thong luong cao, NHUNG phan hoi giat

Muc tieu: du dai de context switch khong dang ke so voi thoi gian lam viec,
          du ngan de nguoi dung khong cam thay giat.

Nguyên tắc thực dụng: slice nên dài hơn nhiều lần chi phí một context switch (để overhead không đáng kể) nhưng đủ ngắn để độ trễ phản hồi nằm dưới ngưỡng con người cảm nhận (~vài chục ms). Đây là lý do time slice điển hình rơi vào khoảng vài ms (dưới tải nặng CFS còn chia nhỏ hơn nữa — sched_latency mặc định ~6 ms chia đều cho các thread đang chạy; các scheduler quantum cố định kiểu cũ mới lên tới vài chục ms) — vùng cân bằng giữa hai thái cực (slice ngắn hơn một tick 10 ms của HZ=100 cần high-resolution timer — sched hrtick).

4. Priority và nice — không phải ai cũng bình đẳng

Không phải mọi thread đáng được chia CPU như nhau. Một tác vụ nền nén video có thể nhường bước cho một tiến trình giao diện đang cần phản hồi tức thì. Linux thể hiện điều này qua nice value.

Nice value chạy từ -20 (ưu tiên cao nhất) tới +19 (ưu tiên thấp nhất), mặc định 0. Tên "nice" nghĩa là "tử tế": nice cao (dương) là thread "tử tế", chịu nhường CPU cho thread khác; nice thấp (âm) là thread "đòi hỏi", được ưu ái. Theo sched(7), mỗi đơn vị chênh lệch nice đổi mức ưu ái khoảng 1,25 lần — tức thread nice 0 nhận CPU gấp ~1,25 lần thread nice +1 khi cả hai cùng tranh.

# Chay mot tac vu nen voi uu tien thap (nhuong CPU cho viec khac)
nice -n 10 ./batch_job.sh

# Dat nice TUYET DOI = 5 cho tien trinh CUA MINH (nhuong bot uu tien): khong can dac quyen
renice -n 5 -p 12345
# Nhung: giam nice (ke ca ve 0 tu so duong) hoac renice tien trinh user khac -> can quyen/root

Lưu ý renice -n 5 đặt nice tuyệt đối bằng 5 (theo util-linux, không phải cộng thêm 5 vào giá trị hiện tại) — nên nó chỉ thực sự "tăng" nice khi giá trị hiện tại đang dưới 5, ví dụ mặc định 0. Chỉ root mới hạ nice xuống âm (tăng ưu tiên vượt mặc định); người dùng thường chỉ tăng nice (nhường bớt). Ngoài SCHED_OTHER (chính sách mặc định dùng nice), Linux còn có chính sách thời gian thực (SCHED_FIFO, SCHED_RR) cho tác vụ cần đảm bảo cứng — nằm ngoài phạm vi bài này.

Nice không phải 'phần trăm CPU'

Nice điều chỉnh tỉ lệ chia tương đối khi có tranh chấp, không đặt một mức CPU tuyệt đối. Một thread nice +19 vẫn có thể dùng 100% CPU nếu không ai khác cần — nice chỉ có tác dụng khi các thread tranh nhau core. Đừng dùng nice để "giới hạn một tiến trình ở 20% CPU"; công cụ cho việc đó là cgroups (CPU quota).

🔮 Tự thiết kế trước khi xem

Trước khi đọc cách CFS chọn thread, thử tự thiết kế. Nếu bạn là scheduler và có ba thread ready với tổng thời gian đã chạy lần lượt 100 ms, 250 ms và 40 ms — bạn cho ai chạy tiếp, và theo tiêu chí gì? Rồi khi có tới 200 thread ready cùng lúc, bạn đặt time slice mỗi lượt dài hay ngắn, và đánh đổi là gì? Viết ra lựa chọn của bạn, rồi đối chiếu với cách CFS làm bên dưới — chỗ bạn nghĩ khác chính là chỗ đáng chú ý.

5. Ý tưởng CFS — công bằng bằng "vruntime"

Từ Linux 2.6.23, scheduler mặc định là CFS — Completely Fair Scheduler (thay cho scheduler "O(1)" cũ). Bạn không cần đọc code kernel; chỉ cần nắm ý tưởng cốt lõi, vì nó giải thích cả tính công bằng lẫn việc I/O-bound được ưu tiên tự nhiên.

5.1 vruntime và cây đỏ-đen

CFS bỏ khái niệm "time slice cứng gán trước". Thay vào đó, nó theo dõi cho mỗi thread một con số: vruntime (virtual runtime) — thời gian CPU thread đó đã thực sự chạy (có điều chỉnh theo nice). Quy tắc chọn cực kỳ đơn giản, đúng như tài liệu kernel viết: CFS luôn chọn chạy thread có vruntime nhỏ nhất — tức thread "đã chạy ít nhất cho tới giờ", thread thiệt thòi nhất.

Cơ chế: CFS giữ mọi thread ready trong một cây đỏ-đen (red-black tree — cây tìm kiếm nhị phân tự cân bằng, thao tác O(log n), phần tử nhỏ nhất luôn ở nhánh trái cùng) sắp theo vruntime. Thread có vruntime nhỏ nhất nằm ở nút trái nhất (leftmost) — CFS lấy nó ra chạy. Khi thread chạy, vruntime của nó tăng dần, nên nó "trôi" sang phải trong cây, nhường chỗ nút-trái-nhất cho thread khác. Kết quả là mọi thread luân phiên tiến về phía "chạy ít nhất", giữ tổng thời gian chạy xấp xỉ nhau — đó là "completely fair".

flowchart TB
  subgraph tree["Red-black tree cac thread ready (sap theo vruntime)"]
    L["Leftmost:<br/>vruntime NHO NHAT<br/>(chay it nhat)"] --- M["..."] --- R["vruntime lon<br/>(vua chay nhieu)"]
  end
  L -->|"CFS chon chay"| RUN["Thread chay,<br/>vruntime tang dan"]
  RUN -->|"troi sang phai,<br/>nhuong leftmost"| tree

5.2 nice và ưu tiên I/O-bound

Hai hệ quả quan trọng của quy tắc này:

  • Nice điều chỉnh tốc độ tăng vruntime. Thread nice thấp (ưu tiên cao) tích vruntime chậm hơn so với thời gian thực nó chạy, nên nó lâu rơi khỏi vị trí "chạy ít nhất" → được chọn thường xuyên hơn. Đó là cách nice biến thành "1,25 lần mỗi đơn vị" một cách mượt mà.
  • Thread hay ngủ (I/O-bound) được ưu tiên tự nhiên. Một thread thường xuyên block chờ I/O tích luỹ rất ít vruntime (vì nó ít chạy). Khi I/O xong và nó quay lại ready, vruntime nhỏ của nó đưa nó lên gần nút-trái-nhất → được chọn nhanh. Vì vậy các tác vụ tương tác (gõ phím, chuột) — vốn I/O-bound — luôn phản hồi nhanh mà không cần luật đặc biệt. Đây là điểm nối thẳng sang bài 04.
CFS đang nhường chỗ cho EEVDF

Tài liệu kernel ghi rõ: "Nowadays, CFS is making room for EEVDF" — scheduler EEVDF (Earliest Eligible Virtual Deadline First) đang dần thay CFS trong các kernel Linux mới. EEVDF giữ tinh thần công bằng dựa trên thời gian ảo nhưng thêm khái niệm "deadline ảo" để kiểm soát độ trễ tốt hơn. Với mục tiêu của bài — hiểu ý tưởng công bằng theo thời gian đã chạy — CFS vẫn là mô hình để nắm; chi tiết EEVDF xem tài liệu kernel scheduler. (Nguồn không nêu số phiên bản cụ thể, nên bài này không khẳng định mốc kernel.)

6. Pitfall của riêng concept này

Nhầm 1 — tưởng while (true) {} treo cả máy:

✅ Trên OS preemptive, timer interrupt đều đặn kéo kernel vào và chia lượt cho thread khác, nên một vòng lặp vô hạn chỉ ngốn phần CPU của nó (tối đa một core nếu đơn luồng), không đóng băng máy. Bạn vẫn di được chuột, mở được terminal, và kill được nó. (Trên OS cooperative đời cũ thì đúng là treo — nhưng đó là quá khứ.)

Nhầm 2 — tưởng nice đặt mức CPU tuyệt đối:

✅ Nice chỉ đổi tỉ lệ chia khi tranh chấp. Thread nice +19 vẫn dùng 100% CPU nếu không ai khác cần. Muốn giới hạn cứng (ví dụ "container này tối đa 2 core"), dùng cgroups CPU quota, không phải nice.

Nhầm 3 — tưởng scheduler "cào bằng round-robin từng lượt":

✅ CFS không xoay vòng theo số lượt, mà cân bằng theo tổng thời gian đã chạy (vruntime). Một thread ngủ nhiều rồi thức dậy được ưu tiên hơn thread vừa chạy liên tục — dù xét theo "số lượt" thì có vẻ không công bằng. Chính điều này làm tác vụ tương tác mượt.

7. 📚 Deep Dive Linux

📚 Deep Dive (tuỳ chọn)

Spec / man page / tài liệu kernel chính thức:

  • sched(7) — tổng quan lập lịch Linux: các chính sách (SCHED_OTHER, SCHED_FIFO, SCHED_RR), nguồn của "CFS mặc định từ 2.6.23" và "mỗi đơn vị nice ≈ 1,25 lần".
  • nice(2)setpriority(2) — API đổi nice value; giới hạn quyền (chỉ root hạ nice âm).
  • Tài liệu kernel — CFS Scheduler Design — nguồn của "CFS luôn chọn thread có vruntime nhỏ nhất" và cây đỏ-đen chọn nút trái nhất; kèm ghi chú CFS nhường chỗ EEVDF.

Ghi chú: tần số timer (HZ = 100/250/1000) và độ dài time slice là tham số cấu hình, không phải hằng số phổ quát — con số trong bài là để định hướng biên độ. CFS thực chất tính slice động (chia một khoảng mục tiêu cho số thread ready) chứ không gán slice cố định; bài này giữ ở mức ý tưởng.

8. Liên hệ các bài khác

9. Tóm tắt

  • Scheduler chọn thread ready nào chạy tiếp trên mỗi core.
  • Lập lịch preemptive dùng timer interrupt (thường 100–1000 lần/giây) để cướp CPU đều đặn — nên while (true) {} không treo được cả máy.
  • Time slice là phần thời gian trước khi preempt; đánh đổi: ngắn = phản hồi nhanh nhưng tốn context switch, dài = thông lượng cao nhưng giật.
  • Nice (-20 tới +19) đổi mức ưu ái ~1,25 lần mỗi đơn vị; chỉ có tác dụng khi các thread tranh nhau core, không đặt mức tuyệt đối.
  • CFS theo dõi vruntime (thời gian đã chạy, điều chỉnh theo nice) và luôn chọn thread vruntime nhỏ nhất qua cây đỏ-đen (nút trái nhất) — công bằng theo tổng thời gian chạy.
  • Nice điều chỉnh tốc độ tăng vruntime; thread I/O-bound (hay ngủ) tích ít vruntime nên được ưu tiên tự nhiên khi thức dậy.
  • CFS đang được thay dần bằng EEVDF trong kernel mới, nhưng ý tưởng công bằng theo thời gian ảo vẫn là mô hình để nắm.

10. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao một chương trình chạy while (true) {} không làm treo cả máy trên Linux, dù nó không bao giờ tự nguyện nhường CPU? Cơ chế nào cứu bạn?
Nhờ lập lịch preemptive dựa trên timer interrupt. Phần cứng phát interrupt đều đặn (100–1000 lần/giây trên Linux); mỗi lần interrupt, CPU nhảy vào kernel bất kể thread đang làm gì, kernel cập nhật thời gian đã chạy và hỏi scheduler có nên preempt không. Vòng lặp while (true) {} không tự nhường, nhưng timer cướp CPU khỏi nó khi hết time slice và cho thread khác chạy. Vì vậy nó chỉ ngốn phần CPU của mình (tối đa một core nếu đơn luồng) — chuột, terminal, các tiến trình khác vẫn được chia lượt và bạn vẫn kill được nó. Nếu Linux dùng lập lịch cooperative (chỉ đổi khi thread tự nhường) thì đúng là vòng lặp này sẽ đóng băng máy — đó là hành vi của các OS đời cũ (Windows 3.x, Mac OS cổ).
Q2
Time slice quá ngắn và quá dài mỗi bên hại gì? Nguyên tắc chọn độ dài hợp lý là gì?
Slice quá ngắn: đổi thread rất thường xuyên → nhiều context switch, mỗi switch tốn chi phí trực tiếp (register) cộng gián tiếp (làm nguội cache/TLB — bài 02). Nếu slice xấp xỉ chi phí một switch, CPU dành phần lớn thời gian đổi việc thay vì làm việc → thông lượng sụt. Slice quá dài: ít switch nên thông lượng tính toán cao, nhưng một thread giữ core lâu khiến các thread khác chờ lâu tới lượt → phản hồi giật (di chuột thấy khựng). Nguyên tắc: slice nên dài hơn nhiều lần chi phí một context switch (để overhead không đáng kể) nhưng đủ ngắn để độ trễ phản hồi dưới ngưỡng con người cảm nhận (~vài chục ms). Vùng cân bằng thường là vài ms tới vài chục ms.
Q3
Bạn chạy nice -n 19 ./job.sh trên máy đang rảnh (không tiến trình nào khác cần CPU). Job này dùng bao nhiêu CPU? Giải thích vì sao, và nice thật sự làm gì.
Job vẫn dùng tới 100% một core dù nice +19 (ưu tiên thấp nhất). Lý do: nice chỉ điều chỉnh tỉ lệ chia tương đối khi có tranh chấp, không đặt mức CPU tuyệt đối. Khi không ai khác cần core, không có tranh chấp để phân xử, nên job "tử tế" này cứ dùng toàn bộ CPU rảnh. Nice chỉ phát huy khi nhiều thread cùng tranh một core: lúc đó thread nice thấp (ưu tiên cao) được chia nhiều hơn — mỗi đơn vị nice đổi khoảng 1,25 lần theo sched(7). Vì vậy nice +19 nghĩa là "nếu có ai khác cần CPU, tôi nhường phần lớn cho họ", chứ không phải "tôi chỉ dùng tối đa X%". Muốn giới hạn cứng mức CPU thì dùng cgroups quota, không phải nice.
Q4
Giải thích quy tắc chọn thread của CFS bằng khái niệm vruntime. Vì sao quy tắc này được gọi là 'công bằng'?
CFS gán cho mỗi thread một con số vruntime — thời gian CPU thread đó đã thực sự chạy (điều chỉnh theo nice). Quy tắc chọn: luôn chạy thread có vruntime nhỏ nhất, tức thread "đã chạy ít nhất cho tới giờ". Cơ chế: mọi thread ready nằm trong một cây đỏ-đen sắp theo vruntime; thread vruntime nhỏ nhất ở nút trái nhất, CFS lấy nó ra chạy. Khi chạy, vruntime của nó tăng dần nên nó "trôi sang phải", nhường vị trí trái nhất cho thread khác. "Công bằng" vì cơ chế này liên tục kéo tổng thời gian chạy của mọi thread xấp xỉ nhau — không thread nào bị bỏ đói lâu, và không thread nào chạy vượt trội trừ khi nice cho phép. Đây là cân bằng theo tổng thời gian đã chạy, khác với round-robin đơn thuần đếm số lượt.
Q5
Vì sao CFS tự nhiên ưu tiên các tác vụ tương tác (gõ phím, di chuột) mà không cần một luật riêng cho chúng?
Vì tác vụ tương tác là I/O-bound: chúng dành phần lớn thời gian blocked chờ sự kiện (chờ bạn gõ phím, chờ chuột) và chỉ chạy một nhúm rất ngắn để xử lý mỗi sự kiện. Do ít chạy, chúng tích luỹ vruntime rất nhỏ. Khi sự kiện tới và thread quay lại ready, vruntime nhỏ của nó đưa nó lên gần nút trái nhất của cây → CFS chọn nó gần như ngay. Kết quả: mỗi lần bạn gõ phím, thread xử lý được chạy tức thì, cảm giác mượt. Ngược lại, một thread tính toán liên tục (CPU-bound) tích vruntime lớn nên bị đẩy sang phải, phải chờ. Vẻ đẹp của thiết kế: sự ưu tiên này rơi ra tự nhiên từ quy tắc "chọn vruntime nhỏ nhất", không cần CFS nhận diện "đây là tác vụ tương tác" hay có luật đặc biệt. Đây chính là cầu nối sang bài 04.
Q6
Một đồng nghiệp mô tả scheduler là 'round-robin: mỗi thread một lượt, xoay vòng đều'. Mô tả này sai ở điểm nào so với CFS?
Round-robin cân bằng theo số lượt: mỗi thread lần lượt được một slice, xoay vòng, bất kể nó đã chạy tổng cộng bao nhiêu. CFS cân bằng theo tổng thời gian đã chạy (vruntime), không phải số lượt. Khác biệt lộ ra rõ nhất với thread hay ngủ: một thread I/O-bound vừa thức dậy có vruntime nhỏ (chạy ít) nên CFS ưu tiên nó ngay, dù xét theo "số lượt" nó không hề tới lượt trước thread CPU-bound. Round-robin thuần sẽ bắt nó xếp hàng cuối như mọi thread khác — làm tác vụ tương tác giật. CFS cũng không phát slice cố định đều nhau: nó tính slice động (chia một khoảng mục tiêu cho số thread ready) và dùng nice để đổi tốc độ tích vruntime. Nói ngắn: round-robin đếm lượt; CFS cân thời gian — và chính "cân thời gian" mới giữ được cả công bằng lẫn phản hồi nhanh.

Bài tiếp theo: CPU-bound vs I/O-bound — chọn số thread thế nào

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