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

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

Hai luồng cùng đụng một dữ liệu — mọi bài toán khó của concurrency bắt đầu từ đây. Lộ trình module: race condition, atomic/mutex, deadlock, IPC.

TL;DR: Khi hai luồng (thread) hoặc hai tiến trình (process) cùng đọc-ghi một dữ liệu, thứ tự đan xen các thao tác của chúng — interleaving — quyết định kết quả. Vì scheduler có thể cắt ngang giữa hai lệnh máy bất kỳ, một vài đan xen "xui" sinh ra kết quả sai: đó là race condition, loại bug xuất hiện một phần triệu lần chạy nên gần như không tái hiện được trên máy dev. Module này đi từ gốc rễ đó tới bốn công cụ để trị: hiểu vì sao count++ không nguyên tử, chặn race bằng atomic hoặc mutex (hoặc tránh chia sẻ ngay từ đầu), chẩn đoán deadlock qua 4 điều kiện Coffman, và cho hai tiến trình nói chuyện an toàn qua IPC (pipe, shared memory, socket).

Vì sao module này tồn tại

Một service chạy tốt suốt sáu tháng trong staging. Lên production, mỗi ngày vài triệu request, và đâu đó một lần trong một triệu có một đơn hàng bị tính sai số dư, hoặc một bộ đếm hụt vài đơn vị. Bạn thêm log, chạy lại 10.000 lần trên máy — không bao giờ ra lỗi. Bạn đọc lại code, từng dòng đều "đúng".

Đây là chữ ký của một race condition. Code không sai theo nghĩa logic tuần tự; nó sai vì bạn ngầm giả định các thao tác chạy nguyên khối, trong khi thực tế scheduler có thể cắt ngang giữa hai lệnh máy (bạn đã thấy cơ chế preemption đó ở Module 3 — Scheduler & time slice). Một count++ tưởng là một bước, thực ra là ba bước — và giữa ba bước đó, một luồng khác chen vào.

Module này không dạy bạn học thuộc "phải synchronized cho an toàn". Nó dạy cơ chế: race sinh ra từ đâu, mỗi công cụ chặn nó bằng cách nào, cái giá phải trả là gì, và khi nào cách tốt nhất là không chia sẻ dữ liệu thay vì khoá.

Sau module này bạn sẽ

  • Explain race condition sinh ra thế nào từ interleaving và thao tác không nguyên tử — chỉ ra được đúng chỗ scheduler chen vào giữa count++.
  • Choose cơ chế đồng bộ đúng: phép toán atomic (CAS), mutex (khoá vùng găng), hay thiết kế tránh chia sẻ ngay từ đầu — và giải thích trade-off chi phí của mỗi lựa chọn.
  • Diagnose deadlock bằng 4 điều kiện Coffman, đọc được thread dump (jstack) chỉ ra vòng chờ, và phòng ngừa bằng lock ordering.
  • Compare các cơ chế IPC — pipe, shared memory, socket — theo chi phí sao chép dữ liệu và mô hình chia sẻ, để chọn đúng khi hai tiến trình cần phối hợp.

Lộ trình module

Bốn bài concept đi theo một mạch nhân-quả, không phải bốn chủ đề rời:

Bài 01 dựng gốc rễ: mổ count++ thành ba bước load/add/store ở mức bytecode, cho hai luồng đua nhau và xem interleaving nào làm mất update. Đây là bài "vì sao có vấn đề". Bài 02 đưa hai công cụ chặn race — atomic (lock-free, dựa trên lệnh CAS phần cứng) và mutex (khoá vùng găng, có thể block) — kèm chi phí thật của mỗi cách và lựa chọn thứ ba thường bị bỏ quên: đừng chia sẻ. Bài 03 cho thấy mặt trái của khoá: dùng nhiều khoá sai thứ tự sinh deadlock — hai luồng chờ nhau vĩnh viễn; bạn học 4 điều kiện Coffman và cách phá. Bài 04 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ữa — IPC (pipe, shared memory, socket) là cầu nối, và message passing là lối thoát khỏi vòng luẩn quẩn khoá/deadlock.

Bài 05 là mini-challenge: một chương trình có cả race lẫn deadlock — bạn tái hiện, chỉ ra interleaving lỗi, và sửa bằng đúng công cụ. Bài 06 là cheat sheet một trang để bookmark.

flowchart LR
  A["01 Race condition<br/>(van de goc)"] --> B["02 Mutex & atomic<br/>(cong cu chan)"]
  B --> C["03 Deadlock<br/>(mat trai cua khoa)"]
  B --> D["04 IPC<br/>(loi thoat: tranh chia se)"]
  C --> E["05 Mini-challenge<br/>(san race + deadlock)"]

Yêu cầu trước khi bắt đầu

  • Hoàn thành Module 3 — Thread & Lập lịch CPU của khoá này, hoặc đã giải thích được thread là gì, context switch xảy ra khi nào, và scheduler preemptive có thể cắt ngang một luồng giữa chừng. Tham chiếu: trạng thái & context switch.
  • Nắm khái niệm tiến trình từ Module 2 — Tiến trình và PCB — mỗi tiến trình có không gian địa chỉ riêng, điều này giải thích vì sao bài IPC cần cơ chế đặc biệt.
  • Đọc được code Java cơ bản (thread, vòng lặp). Module dùng Java 21 làm ngôn ngữ minh hoạ, kèm C/bash khi nói về IPC mức OS.

Time budget

  • Module: ~1 giờ 20 phút đọc + code-along.
BàiChủ đềPhút
00Tổng quan module6
01Race condition12
02Mutex & atomic13
03Deadlock13
04IPC — pipe, shared memory, socket12
05Mini-challenge — săn race và deadlock18
06Tổng kết & cheat sheet6

Cách học module này hiệu quả

  • Chạy demo race thật. Bài 01 có chương trình hai luồng đếm — chạy nó nhiều lần, xem con số cuối khác nhau mỗi lần. Không gì thuyết phục bằng thấy tận mắt kết quả không xác định.
  • Vẽ interleaving ra giấy. Khi đọc một bug race, tự kẻ hai cột (luồng A, luồng B) và điền thứ tự các bước load/add/store. Bug lộ ra ngay khi bạn thấy hai load đọc cùng một giá trị cũ.
  • Với deadlock, dựng cây chờ. Bài 03 dạy vẽ đồ thị "ai giữ khoá gì, ai chờ khoá gì" — một vòng kín trong đồ thị đó chính là deadlock.
  • Đừng bỏ qua khối "Áp dụng vào code của bạn" cuối mỗi bài — đó là chỗ cơ chế biến thành quyết định thiết kế thật.

Bài tiếp theo: Race condition — khi hai luồng giẫm chân nhau

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