Hệ điều hành & Tiến trình/Tổng kết module — Đồng bộ & Phối hợp
28/28
Bài 28 / 28~6 phútĐồng bộ & Phối hợpMiễn phí lượt xem

Tổng kết module — Đồng bộ & Phối hợp

Cheat sheet race, mutex/atomic (CAS), deadlock (Coffman, lock ordering), chi phí futex và IPC; kèm glossary, pitfall và self-assessment module.

TL;DR: Module 4 đi từ vì sao concurrency khó — hai luồng đụng một dữ liệu, interleaving sinh kết quả sai — tới bốn công cụ để trị. Bạn hiểu count++ là ba bước nên race sinh ra từ đó; chọn được atomic (thao tác đơn) hay mutex (chuỗi nhiều biến) hay tránh chia sẻ hẳn; chẩn đoán deadlock qua 4 điều kiện Coffman và phá bằng lock ordering; và cho hai tiến trình phối hợp qua IPC (pipe/shared memory/socket) với đánh đổi nhanh-mà-phải-khoá so với chậm-hơn-mà-sạch. Đây là trang một để bookmark.

Đã đi qua những gì

Module bắt đầu từ một câu hỏi khó chịu: vì sao code "đúng từng dòng" lại sai ở production một phần triệu lần chạy? Câu trả lời — race condition — nằm ở chỗ count++ không phải một bước mà là ba (load/add/store), và scheduler preemptive có thể cắt ngang giữa chúng. Hai luồng cùng đọc giá trị cũ, cùng ghi, một update biến mất. Bug này timing-dependent, là Heisenbug, nên không thể bắt bằng test — phải phòng bằng thiết kế.

Rồi bạn học cách phòng. Hai công cụ trực tiếp: atomic (dựa trên CAS phần cứng, lock-free, hợp cho thao tác đơn một biến) và mutex (khoá critical section, block luồng khác, bảo vệ được chuỗi nhiều biến). Chi phí của chúng nằm ở tranh chấp, không ở bản thân khoá: uncontended thì gần như miễn phí, contended thì phải park/wake qua futex tốn hàng chục tới hàng trăm lần hơn. Và lựa chọn thứ ba, thường tốt nhất: đừng chia sẻ — immutable, thread confinement, message passing.

Khoá giải quyết race nhưng mở ra rủi ro mới: deadlock. Bạn học nó cần cả bốn điều kiện Coffman (mutual exclusion, hold-and-wait, no preemption, circular wait), nên phá một là đủ — và cách thực dụng nhất là phá circular wait bằng lock ordering. Khi đã lỡ deadlock, jstack in thẳng "Found one Java-level deadlock" và chỉ ra vòng chờ.

Cuối cùng, bạn bước ra khỏi một tiến trình: khi hai tiến trình riêng cần phối hợp, chúng không chia sẻ biến được nên cần IPC. Pipe (dòng byte một chiều), shared memory (nhanh nhất, tự đồng bộ), socket (linh hoạt nhất, local + mạng). Điểm chốt nối lại cả module: pipe/socket là message passing nên tránh được race/deadlock, còn shared memory mua tốc độ bằng cách chia sẻ và chấp nhận cái giá đồng bộ — cùng một đánh đổi xuyên suốt.

flowchart LR
  R(("Dong bo va Phoi hop"))

  R --> RC["Race condition"]
  RC --> RC1["count++ = load/add/store"]
  RC --> RC2["Interleaving sinh lost update"]
  RC --> RC3["Timing-dependent Heisenbug"]
  RC --> RC4["Khong bat duoc bang test"]

  R --> CC["Cong cu chan"]
  CC --> CC1["Atomic CAS lock-free"]
  CC --> CC2["Mutex khoa critical section"]
  CC --> CC3["Chi phi o tranh chap (futex)"]
  CC --> CC4["Lua chon 3: dung chia se"]

  R --> DL["Deadlock"]
  DL --> DL1["4 dieu kien Coffman"]
  DL --> DL2["Circular wait qua lock nguoc"]
  DL --> DL3["Pha bang lock ordering"]
  DL --> DL4["jstack thread dump"]

  R --> IPC["IPC lien tien trinh"]
  IPC --> IPC1["Pipe byte stream 1 chieu"]
  IPC --> IPC2["Shared memory nhanh nhat can sync"]
  IPC --> IPC3["Socket linh hoat local + mang"]
  IPC --> IPC4["Message passing tranh khoa"]

🗺️ Cheat sheet

Khái niệmCốt lõiPitfall / khi nào dùng
Race conditionKết quả phụ thuộc interleaving; count++ ba bước bị cắt ngangKhông bắt bằng test; phải phòng bằng thiết kế
Critical sectionĐoạn chạm shared mutable state cần chạy nguyên khốiGiữ nhỏ nhất; không chứa I/O/blocking
Atomic (CAS)"Nếu vẫn là X thì đổi thành Y, không thì retry" — lock-freeChỉ nguyên tử một biến; nhiều biến → không đủ
MutexKhoá critical section, luồng khác block chờBảo vệ chuỗi nhiều biến; đắt khi tranh chấp
Chi phí đồng bộUncontended ~ns; contended park/wake qua futex ~hàng chục–trăm lần hơnTối ưu = giảm tranh chấp, không phải né khoá
Đừng chia sẻImmutable / thread-local / message passingAn toàn nhất; không chia sẻ thì không cần khoá
DeadlockVòng chờ khép kín, treo vĩnh viễnCần cả 4 điều kiện Coffman; phá 1 là đủ
Lock orderingLuôn lấy khoá theo cùng thứ tự toàn cụcPhá circular wait; chỉ một chỗ lấy ngược là hỏng
tryLock(timeout)Chờ có hạn rồi buông và thử lạiPhá no-preemption; thêm backoff tránh livelock
jstack / thread dumpẢnh chụp luồng + khoá; JVM tự chỉ deadlockChỉ thấy deadlock qua khoá JVM biết
PipeDòng byte một chiều, đệm 64 KiB, | shellKhông ranh giới message; tự thêm framing
Shared memoryCùng vùng nhớ vật lý, nhanh nhấtPhải tự đồng bộ (semaphore); kéo lại race
SocketHai chiều, cùng API local (AF_UNIX) + mạngChậm hơn shm nhưng message passing sạch

📖 Glossary module

Thuật ngữĐịnh nghĩa 1 câu
Race conditionLỗi khi tính đúng đắn phụ thuộc thứ tự đan xen các thao tác của nhiều luồng
InterleavingThứ tự các bước của nhiều luồng bị scheduler xen kẽ khi chạy
Atomicity (nguyên tử)Tính chất một thao tác chạy trọn vẹn hoặc không, không bị cắt ngang
Lost updateHai luồng cùng đọc giá trị cũ rồi cùng ghi, làm một lần cập nhật biến mất
HeisenbugBug biến mất khi quan sát (thêm log/debugger đổi timing) — đặc trưng của race
Critical sectionĐoạn code truy cập shared mutable state phải chạy nguyên khối
Mutual exclusionĐảm bảo tối đa một luồng ở trong critical section tại một thời điểm
CAS (compare-and-swap)Lệnh phần cứng nguyên tử: đổi giá trị chỉ khi nó vẫn bằng giá trị kỳ vọng
Lock-freeCơ chế không block: luồng chậm/treo không chặn luồng khác tiến tới
MutexKhoá đảm bảo mutual exclusion; luồng không lấy được thì block chờ
Reentrant lockKhoá cho phép luồng đang giữ lấy lại chính nó mà không tự deadlock
Contended / uncontendedCó / không có luồng khác đang tranh cùng khoá — quyết định chi phí
FutexFast userspace mutex trên Linux: đường không tranh chấp ở user space, chỉ chờ mới vào kernel
DeadlockNhóm luồng kẹt vĩnh viễn vì mỗi luồng giữ khoá và chờ khoá luồng khác giữ
Điều kiện CoffmanBốn điều kiện cần đồng thời cho deadlock: mutual exclusion, hold-and-wait, no preemption, circular wait
Circular waitChuỗi luồng chờ vòng tròn T1→T2→...→T1, mỗi luồng chờ tài nguyên luồng kế giữ
Lock orderingLuôn lấy khoá theo cùng một thứ tự toàn cục để phá circular wait
LivelockCác luồng không kẹt cứng nhưng cứ nhường nhau mãi, chạy mà không tiến
IPCInter-process communication — cơ chế cho các tiến trình trao đổi dữ liệu
PipeKênh IPC dòng byte một chiều qua bộ đệm kernel
Shared memoryVùng nhớ vật lý được nhiều tiến trình ánh xạ chung — IPC nhanh nhất
SocketKênh IPC hai chiều dùng chung API cho local (AF_UNIX) và mạng (TCP/UDP)
Message passingMô hình mỗi bên giữ dữ liệu riêng, gửi bản sao — tránh chia sẻ trạng thái

⚠️ Pitfall tổng hợp

1. Tưởng thao tác "một dòng" là nguyên tử:

count++;              // SAI: 3 buoc load/add/store, KHONG nguyen tu
balance += amount;    // SAI: read-modify-write

count.incrementAndGet();   // DUNG: AtomicLong, nguyen tu

2. Dùng atomic cho bất biến trải nhiều biến:

balanceA.addAndGet(-amount);
balanceB.addAndGet(+amount);   // SAI: hai atomic rieng KHONG thanh mot khoi

synchronized (lock) {          // DUNG: mutex bao ve ca chuoi
    balanceA -= amount; balanceB += amount;
}

3. Khoá hai object theo thứ tự khác nhau → deadlock:

void f() { synchronized (a) { synchronized (b) { ... } } }
void g() { synchronized (b) { synchronized (a) { ... } } }  // SAI: nguoc thu tu

// DUNG: lock ordering theo id/hash toan cuc, luon a truoc b o moi noi

4. Giữ khoá khi làm I/O hoặc gọi callback:

synchronized (lock) {
    var data = fetchFromNetwork();   // SAI: giu khoa khi I/O -> moi luong ket
    listener.onChange(this);         // SAI: callback co the lay khoa khac -> deadlock an
}
// DUNG: lam viec cham NGOAI khoa; chi khoa dung luc cham shared state

5. Dùng shared memory mà quên đồng bộ:

// SAI: hai tien trinh cung ghi vung mmap ma khong semaphore -> race lien tien trinh
strcpy(ptr, data);
// DUNG: bao ve bang POSIX semaphore hoac pthread mutex PROCESS_SHARED

6. Giả định pipe/SOCK_STREAM giữ ranh giới message:

SAI: doc mot lan mong nhan tron mot ban ghi -> byte stream khong dam bao
DUNG: tu them framing (dau phan cach hoac do dai prefix), hoac dung SOCK_DGRAM

✅ Self-assessment

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

  • Explain được race condition sinh ra thế nào từ interleaving và thao tác không nguyên tử — mổ được count++ thành ba bước load/add/store và chỉ đúng chỗ scheduler chen vào gây lost update.
  • Choose được cơ chế đồng bộ đúng giữa atomic, mutex, hay thiết kế tránh chia sẻ — giải thích được vì sao atomic đủ cho một biến còn mutex cần cho chuỗi nhiều biến, và vì sao "đừng chia sẻ" thường tốt nhất.
  • Diagnose được deadlock bằng 4 điều kiện Coffman và phòng ngừa bằng lock ordering — đọc được thread dump jstack và giải thích vì sao phá circular wait là đủ.
  • Compare được pipe, shared memory và socket theo chi phí sao chép và mô hình chia sẻ — chọn được cơ chế đúng cho một tình huống và giải thích vì sao message passing tránh được race/deadlock.

🚀 What's next

Bạn vừa hoàn thành Module 4 — Đồng bộ & Phối hợp, cũng là module cuối của khoá Hệ điều hành & Tiến trình (cs-os-process) — course tier 3 của track Máy tính cho Lập trình viên.

Nhìn lại cả khoá: bạn đã đi từ ranh giới kernel/user và system call (Module 1), qua vòng đời tiến trình fork/exec/wait và signal (Module 2), tới thread/scheduler/context switch (Module 3), và khép lại bằng bốn bài toán phối hợp ở module này. Bốn module cho bạn khả năng nhìn thấy ai thật sự chạy code của bạn — và trả giá gì khi nhiều thứ cùng chạy.

Tiếp theo — đào sâu concurrency ở tầng ngôn ngữ: những gì học ở đây (race, atomic/CAS, mutex, deadlock) được hiện thực hoá đầy đủ trong Java. Khoá Java Internals — module Concurrency cơ bản đưa các khái niệm này lên API thật: thread-safety, volatile & synchronized, Atomic & CAS, và ReentrantLock & Condition. Nếu bạn muốn hiểu bên trong JVM các công cụ đồng bộ này hoạt động thế nào, đó là điểm đến tiếp theo.

Sắp ra: khoá tier 4 của track — I/O & Tài nguyên (cs-io-resources) — sẽ nối tiếp chủ đề file descriptor, blocking vs non-blocking I/O, và event loop. Trong lúc chờ, hãy củng cố bằng cách chạy lại mini-challenge bài 05 và thử ép deadlock lộ ra bằng jstack.

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/ — phần Concurrency: Chapter 28 Locks (xây khoá từ test-and-set/CAS) và Chapter 32 Concurrency Bugs (atomicity/order-violation + deadlock). Đọc kèm module này cho góc nhìn OS.
  • Linux man pagespipe(7), shm_overview(7), unix(7), futex(2) — nguồn chuẩn khi viết code IPC/đồng bộ thật.
  • "Java Concurrency in Practice" (Brian Goetz, 2006) — kinh điển về thread-safety, atomic, lock, và các pitfall race/deadlock trong Java; cầu nối tự nhiên sang khoá Java Internals.
  • Coffman, Elphick, Shoshani, "System Deadlocks" (ACM Computing Surveys, 1971) — bài báo gốc định nghĩa bốn điều kiện deadlock mang tên Coffman.

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