bài về lát thời gian ta thấy khi nhiều tiến trình tranh một CPU, bộ lập lịch xoay vòng cho mỗi cái một lát ~3 ms. Nhưng nó có chia đều cho mọi tiến trình không? Không nhất thiết — Linux cho ta một núm để nói "tiến trình này quan trọng hơn cái kia": nice. Câu hỏi thực tế: đặt nice khác nhau thì hai tiến trình đua nhau chia CPU theo tỉ lệ nào? Bài này đo trực tiếp — và phát hiện con số nice của một tiến trình, đứng một mình, chẳng nói lên điều gì.

nice và ưu tiên

nice là gì

Mỗi tiến trình có một giá trị nice, đi từ -20 (giành CPU nhất) tới +19 (nhường nhịn nhất). Tên gọi "nice" nghĩa là "tử tế": nice cao thì tiến trình tử tế, nhường CPU cho kẻ khác; nice thấp thì nó giành. Mặc định mọi tiến trình là nice 0. Quy tắc: số càng nhỏ, ưu tiên càng cao, càng được nhiều CPU.

Bên trong, bộ lập lịch không dùng thẳng con số nice mà quy nó thành một trọng số (weight). Bảng trọng số của Linux được thiết kế sao cho mỗi bậc nice đổi trọng số khoảng 1,25 lần: nice 0 có trọng số 1024, nice 1 khoảng 820, nice -1 khoảng 1277, và cứ thế. Khi nhiều tiến trình tranh một CPU, mỗi cái nhận phần CPU tỉ lệ với trọng số của nó trên tổng trọng số. Đó là lý thuyết; giờ đo xem thực tế có khớp không — và nhớ bài học trước: phải ghim các tiến trình vào cùng một CPU thì chúng mới thật sự tranh nhau để có gì mà chia.

Đo: phần CPU chia theo trọng số nice

Tôi chạy hai tiến trình bận (busy loop) ghim vào cùng CPU 0, đặt một cái nice 0 và cái kia nice tăng dần, rồi đo thời gian CPU mỗi cái tiêu thụ trên cùng một khoảng 4 giây thực:

Cặp nice Phần CPU Tỉ lệ
0 vs 0 50% / 50% 1 : 1
0 vs 5 75% / 25% ~3 : 1
0 vs 10 90% / 10% ~9 : 1
0 vs 19 98,5% / 1,5% ~66 : 1

Các con số khớp gần như hoàn hảo với bảng trọng số. Hai tiến trình cùng nice chia đôi 50/50. Chênh 5 bậc nice cho tỉ lệ ~3:1 — đúng bằng 1,25^5 ≈ 3,05. Chênh 10 bậc cho ~9:1 (1,25^10 ≈ 9,3). Chênh tối đa 19 bậc cho ~66:1 (1,25^19 ≈ 68), tức tiến trình nice 19 gần như bị bỏ đói, chỉ nhặt được 1,5% CPU khi có kẻ nice 0 tranh cùng. Vậy nice không phải một công tắc "quan trọng/không quan trọng" mà là một núm xoay tỉ lệ khá mịn, và phần CPU chia theo đúng tỉ lệ trọng số.

Một chi tiết đáng ghi: khi tôi thử đặt nice âm (dưới 0, tức giành thêm CPU), lời gọi bị từ chối với "Permission denied". Hạ nice xuống dưới 0 là xin nhiều hơn phần mặc định, nên cần quyền CAP_SYS_NICE (thường là root); còn tăng nice (nhường bớt) thì ai cũng làm được. Bạn được phép tử tế hơn tùy thích, nhưng muốn tham lam hơn thì phải xin phép.

Một lần tôi đo hớ: nice là tương đối, không tuyệt đối

Nhìn bảng trên, tôi suýt rút ra một kết luận sai theo bản năng: "à, nice 10 là mức ưu tiên thấp, tiến trình nice 10 sẽ luôn được ít CPU". Tức là tôi gán cho con số nice tuyệt đối một ý nghĩa cố định về lượng CPU. Để kiểm, tôi đo lại nhưng giữ nguyên hiệu nice và đổi giá trị tuyệt đối:

nice  0 vs  5  ->  75% / 25%
nice  5 vs 10  ->  75% / 25%
nice 10 vs 15  ->  75% / 25%

Ba cặp, ba giá trị tuyệt đối hoàn toàn khác nhau, nhưng phần chia y hệt nhau: 75/25. Vì cả ba đều cách nhau đúng 5 bậc nice. Điều này lật ngược trực giác của tôi: phần CPU không phụ thuộc con số nice tuyệt đối, mà chỉ phụ thuộc HIỆU nice giữa hai kẻ đang tranh. Một tiến trình nice 10 hoàn toàn có thể là kẻ ưu tiên cao — nếu đối thủ của nó là nice 15, nó chiếm 75% CPU. Con số nice của một tiến trình, đứng một mình, chẳng cho biết nó sẽ được bao nhiêu CPU; điều đó chỉ lộ ra khi biết nó đang tranh với ai.

Bài học đo lường: ưu tiên là chuyện tương đối, không phải một thuộc tính tuyệt đối của tiến trình. Tôi đã suýt đóng khung "nice 10 = chậm" như một sự thật cố định, nhưng phép đo cho thấy cùng một nice 10 có thể là kẻ mạnh hay kẻ yếu tùy hàng xóm. Đây là kiểu lỗi khi ta gán ý nghĩa tuyệt đối cho một đại lượng vốn chỉ có nghĩa trong so sánh — họ hàng với cái bẫy "cân bằng theo chỉ số nào" và "nhanh so với cái gì" của các bài trước. Tôi còn vấp thêm một giả định sai về môi trường: tôi đoán tính năng autogroup của nhân (gom tiến trình theo phiên đăng nhập, chia CPU đều giữa các phiên rồi mới tới nice) sẽ làm loạn phép chia khi hai tiến trình ở hai phiên khác nhau — nhưng đo ra, bên trong cgroup của container thì autogroup không chen vào, phần chia vẫn theo đúng nice. Lại một lần phải đo chứ đừng tin giả định về môi trường tự dựng.

Vì sao điều này quan trọng khi lập trình

Hệ quả đầu tiên là nice là công cụ đúng cho các tác vụ nền không gấp. Một job sao lưu, nén log, build ngầm, hay xử lý hàng loạt chạy dưới nền không nên tranh CPU ngang ngửa với tiến trình phục vụ người dùng. Đặt nó nice cao (ví dụ nice -n 19 ./job-nen) khiến nó tự động nhường CPU mỗi khi có việc quan trọng hơn, mà vẫn chạy hết tốc lực khi máy rảnh — vì nice chỉ chia phần khi có tranh chấp, máy rảnh thì kẻ nice 19 vẫn dùng 100% CPU. Đây là cách "thân thiện" để chạy việc nặng mà không làm giật hệ thống.

Hệ quả thứ hai là nice chỉ có tác dụng khi thật sự có tranh CPU, và tác dụng là tương đối. Nếu máy dư CPU (số tiến trình bận ít hơn số nhân), nice gần như vô nghĩa — ai cũng được phục vụ đủ, như bài lát thời gian đã đo. Và khi có tranh, điều quyết định là hiệu nice giữa các bên, không phải giá trị tuyệt đối: hạ nice một tiến trình xuống trong khi mọi tiến trình khác cũng nice thấp thì chẳng thay đổi gì. Muốn một job "nhường" thật sự, phải đặt nó nice cao hơn hẳn so với những gì nó đang tranh cùng.

Hệ quả thứ ba, rộng hơn: đừng gán ý nghĩa tuyệt đối cho một con số vốn chỉ có nghĩa khi so sánh. Con số mang theo: hai tiến trình tranh một CPU chia phần theo trọng số nice (mỗi bậc ~1,25 lần): 0 vs 5 ra 75/25, 0 vs 10 ra 90/10, 0 vs 19 ra 98,5/1,5 — nhưng phần chia chỉ phụ thuộc HIỆU nice, nên nice 10 vs 15 chia y hệt nice 0 vs 5, và một tiến trình nice 10 vẫn là "ưu tiên cao" nếu đối thủ nice 15. nice là một núm xoay tỉ lệ tinh tế; dùng nó cho tác vụ nền, nhưng nhớ nó chỉ nói lên điều gì đó khi đặt cạnh kẻ mà nó đang tranh.

Thử ba mươi giây

Xem nice chia CPU tận mắt: chạy hai vòng lặp bận ghim vào cùng một CPU với nice khác nhau — taskset -c 0 nice -n 0 sh -c 'while :; do :; done' &taskset -c 0 nice -n 5 sh -c 'while :; do :; done' & — rồi mở top (hoặc htop): bạn sẽ thấy cái nice 0 chiếm ~75% CPU 0 và cái nice 5 chỉ ~25%, đúng tỉ lệ 3:1. Đổi cặp nice thành 5 và 10 (vẫn hiệu 5) và chạy lại — tỉ lệ vẫn 3:1, chứng minh chỉ hiệu nice mới quan trọng. Nhớ kill %1 %2 để dọn. Xem nice của một tiến trình bất kỳ bằng cột NI trong top, hay ps -o pid,ni,comm. Đó là núm ưu tiên bài này đo — tương đối, không tuyệt đối.