+1

Hệ điều hành (Phần 1): Tìm hiểu về Process, Thread, Multi-Process và Multi-Thread

Phần một này chúng ta sẽ tìm hiểu về ProcessThread, các cơ chế Multi-ProcessMulti-Thread, tại sao máy tính lại cần có 2 ông này, các ưu nhược điểm, giải thích dễ hiểu cho anh em có thể nắm bắt ngay lập tức, thay vì phải đi tìm hiểu lại từ đầu

Nếu thấy hay, kết nối với mình tại LinkedIn.

Những kiến thức nền tảng, luôn là một phần quan trọng trong thế giới lập trình, kể cả hiện nay, AI đã len lỏi vào hết các công việc của lập trình viên. Nắm rõ kiến thức nền tảng, bạn sẽ dễ dàng hơn trong việc phát triển, debug, cũng như sử dụng AI một cách hiệu quả.

Hôm nay, mình sẽ có một bài deep dive về Process và Thread, những thứ nằm dưới lớp hệ điều hành của chúng ta, một trong những kiến thức nền tảng phải nắm chắc khi bạn làm việc với máy tính, giúp bạn hiểu được, khi một ứng dụng chạy, thì có cái gì thực sự diễn ra bên dưới. Bắt đầu thôi.

Process là gì ?

Hiểu đơn giản, thì process là một chương trình phần mềm đang được thực thi trên máy tính. Một phần mềm có thể tạo ra nhiều process khác nhau. Trên một máy tính, có thể có nhiều process của nhiều phần mềm khác nhau cùng tồn tại.

Dễ hiểu hơn, với Windows, khi bạn chạy một trình duyệt web như Chrome chẳng hạn nó sẽ tạo nhiều process khác nhau cho từng tab, extension và các thành phần hệ thống, hoặc bạn bật game lên chơi, thì nó sẽ tạo ra một process riêng biệt. image.png

Mỗi process có một process ID riêng, có dữ liệu và trạng thái riêng. Mỗi process hoạt động trong không gian nhớ riêng, không thể truy cập trực tiếp dữ liệu của process khác, trừ khi có cơ chế chia sẻ được hệ điều hành cho phép (IPC).

Để lưu dữ liệu của một process, OS sẽ sử dụng một cấu trúc dữ liệu gọi là Process Control Block (PCB). Mỗi PCB gắn với một PID riêng. PCB bao gồm các thông tin sau:

  • Process ID (PID): Số nguyên xác định định danh của tiến trình
  • State: Trạng thái hiện tại của tiến trình. Một tiến trình không phải lúc nào cũng ở trạng thái Running. Nó có thể ở trạng thái Ready (đang chờ CPU), Waiting (đang chờ I/O hoặc một sự kiện nào đó), hoặc Terminated.
  • Pointer: Thông tin liên kết tới các process liên quan, chẳng hạn như tiến trình cha
  • Priority: Độ ưu tiên của tiến trình, giúp cho vi xử lý xác định thứ tự thực hiện
  • Program Counter (PC): Con trỏ lưu địa chỉ của chỉ dẫn tiếp theo mà tiến trình sẽ thực thi. Thông tin này rất quan trọng trong context switch, vì nó giống như một “bookmark” giúp CPU biết chính xác vị trí đang thực hiện dở để tiếp tục khi quay lại tiến trình đó..
  • CPU Registers: Các thanh ghi mà process cần sử dụng để thực thi. Chúng lưu trữ trạng thái tạm thời của CPU trong lúc tiến trình đang chạy.
  • I/O Information: Thông tin về các thiết bị đọc-ghi mà tiến trình cần sử dụng
  • Accounting Information: Chứa các thông tin về việc sử dụng CPU như thời gian sử dụng, định danh.

Thread là gì ?

Thread là một đơn vị thực thi “nhẹ” nằm bên trong một process. Nếu process là một ngôi nhà, thì các thread là những người sống bên trong — họ dùng chung nhà bếp và phòng khách (heap / không gian địa chỉ), nhưng mỗi thread lại có phòng ngủ riêng (stack / thanh ghi).

  • Đơn vị nhỏ nhất: Thread là chuỗi lệnh nhỏ nhất mà bộ lập lịch (Scheduler) của hệ điều hành có thể quản lý và lên lịch độc lập.
  • Tài nguyên dùng chung: Các thread chia sẻ code, dữ liệu và tài nguyên của hệ điều hành Điều này giúp giao tiếp nhanh hơn nhưng cũng dễ gây ra race condition, khi hai thread cùng lúc cố thay đổi cùng một dữ liệu, dẫn đến lỗi.
  • Hiệu quả: Việc chuyển ngữ cảnh giữa các thread thường nhanh hơn so với giữa các process, do chúng dùng chung không gian địa chỉ (heap/ address space). image.png

Hardware Threads và Software Threads

  • Hardware Threads (vật lý): Đây là các đơn vị thực thi trên CPU. Ví dụ, Apple M4 có 10 cores sẽ cung cấp 10 hardware threads. Số lượng cores này quyết định số lượng thread có thể chạy song song thực sự. Về cơ bản là một tập hợp các thanh ghi trên lõi CPU cho phép nó lưu giữ trạng thái của một software thread.
  • OS / Software Threads (logic): Đây là các thread do kernel quản lý. Bạn có thể tạo ra hàng nghìn software threads, và hệ điều hành sẽ chia thời gian chạy của chúng trên hardware threads (ví dụ với Apple M4 là trên 10 hardware threads).

Ví dụ với Java

Trước đây, 1 Java Thread thường tương ứng với 1 OS Thread (Platform Thread). Còn Virtual Threads hiện đại (Project Loom) cho phép hàng nghìn Java thread chạy trên một số lượng nhỏ OS thread hơn, giúp các ứng dụng quy mô lớn hoạt động hiệu quả hơn.

Thread được tạo như thế nào

Khi mỗi thread được tạo ra, nó sẽ có trạng thái thực thi riêng, bao gồm một Instruction Pointer (IP), tức là con trỏ lệnh dùng để xác định vị trí của lệnh tiếp theo mà thread sẽ thực thi.

Nhờ có trạng thái thực thi riêng này, khi CPU thực hiện Context Switch giữa các thread, mỗi thread có thể tiếp tục đúng tại vị trí nó đã dừng lại, thay vì phải bắt đầu lại từ đầu.

Thread Control Block (TCB)

Cũng giống như process có PCB, thread thường sẽ có một TCB hoặc một cấu trúc tương đương dành riêng cho thread. Cấu trúc này thường nhỏ và nhẹ hơn PCB.

Một TCB có thể bao gồm:

  • Thread ID.
  • Stack Pointer.
  • Instruction Pointer.
  • State.
  • Các giá trị thanh ghi.

Vì sao nó “rẻ” hơn so với process

Việc chuyển đổi giữa các thread trong cùng một process thường nhanh hơn việc chuyển đổi giữa các process, vì hệ điều hành không cần chuyển sang một không gian địa chỉ khác. Các thread trong cùng một process dùng chung cùng một memory map, nên chi phí chuyển đổi thấp hơn.

Multi-thread

Đa luồng là khả năng của CPU hoặc của một tiến trình duy nhất trong việc cung cấp nhiều luồng thực thi đồng thời. Thay vì chỉ đi theo một dòng lệnh, tiến trình có thể tách thành nhiều hướng thực thi khác nhau. image.png

Ưu điểm

  • Không bị chặn: Đây là điều rất quan trọng với các ứng dụng hiện đại. Luồng chính sẽ xử lý tương tác người dùng như click hay cuộn trang, trong khi các luồng phụ xử lý các tác vụ nặng như gọi API hoặc truy vấn cơ sở dữ liệu.
  • Tận dụng tài nguyên tốt hơn: Trên bộ xử lý nhiều nhân, đa luồng cho phép hệ điều hành chạy nhiều tác vụ song song khi có thể.
  • Tiết kiệm chi phí: Việc tạo thread rẻ hơn process vì thread không cần một không gian nhớ hoàn toàn mới. Các thread trong cùng một process sẽ dùng chung heap hiện có.

Nhược điểm

  • Race condition: Xảy ra khi hai thread cùng đọc hoặc ghi vào cùng một biến dùng chung tại cùng một thời điểm.

Ví dụ: cả hai thread đều thấy số dư là $100, cả hai cùng cộng thêm $10, và thay vì ra $120, kết quả cuối cùng có thể chỉ là $110 vì một lần cập nhật đã ghi đè lên lần kia.

  • Deadlock: Thread A giữ Tài nguyên 1 và chờ Tài nguyên 2, trong khi Thread B giữ Tài nguyên 2 và chờ Tài nguyên 1. Cả hai sẽ chờ nhau mãi mãi.
  • Starvation: Những thread có độ ưu tiên thấp có thể không bao giờ được chạy nếu các thread ưu tiên cao luôn chiếm CPU.
  • Độ phức tạp khi kiểm thử: Lỗi trong chương trình đa luồng thường được gọi là Heisenbug — chúng biến mất khi bạn cố quan sát, vì việc debug làm thay đổi timing.
  • Chi phí ẩn: Việc chuyển ngữ cảnh và đồng bộ bộ nhớ, như volatile hoặc synchronized trong Java, tạo ra overhead có thể khiến một chương trình đơn giản chạy chậm hơn cả phiên bản một luồng.

Vì sao không thể nhanh mãi

Đa luồng không phải lúc nào cũng làm chương trình nhanh hơn. Điều này được mô tả chính thức bởi Định luật Amdahl, cho rằng mức tăng tốc của chương trình bị giới hạn bởi phần tuần tự — phần không thể chạy song song. Nếu 20% code của bạn bắt buộc phải chạy tuần tự, thì chương trình của bạn sẽ không bao giờ nhanh hơn 5 lần, dù bạn có thêm bao nhiêu thread đi nữa. Cái định luật này anh em tìm hiểu thêm trên mạng nhé hoặc hỏi AI nhé, cũng dễ hiểu lắm.

Một chi phí ẩn khác là overhead của context switching. Khi CPU chuyển từ Thread A sang Thread B, nó phải lưu trạng thái hiện tại như thanh ghi và stack pointer, rồi nạp trạng thái mới. Nếu có quá nhiều thread, CPU có thể tốn nhiều thời gian để **chuyển qua lại **hơn là “làm việc” thực sự.

Multi-process

Multi-process là mô hình trong đó một chương trình hoặc hệ thống sử dụng nhiều process độc lập để xử lý công việc. Mỗi process có không gian địa chỉ ảo riêng, có trạng thái riêng, và không chia sẻ trực tiếp dữ liệu với process khác như thread. Vì vậy, khi một process gặp lỗi hoặc bị crash, các process còn lại thường vẫn có thể tiếp tục chạy bình thường. image.png

Hiểu đơn giản hơn, multi-process giống như việc chia một ứng dụng lớn thành nhiều “phòng làm việc” riêng biệt. Mỗi phòng có một nhiệm vụ riêng, có người làm riêng, tài liệu riêng, và ít phụ thuộc lẫn nhau. Điều này giúp hệ thống an toàn hơn và dễ tách biệt hơn.

Ví dụ:

  • Trình duyệt có thể dùng nhiều process cho tab, extension, và network.
  • Một server như Nginx hoặc PostgreSQL (nguyên lý Process by connection) cũng có thể dùng nhiều process để xử lý các nhiệm vụ khác nhau.
  • Một chương trình Python có thể tạo ra nhiều process để xử lý các tác vụ nặng trên nhiều core CPU.

Vì sao dùng multi-process

Dùng multi-process khi:

  • Công việc có thể chia nhỏ thành nhiều phần độc lập.
  • Muốn tận dụng nhiều core CPU để tăng hiệu năng.
  • Muốn cô lập lỗi, tránh một phần lỗi ảnh hưởng tới toàn bộ ứng dụng.
  • Muốn tránh giới hạn của thread trong một số runtime hoặc ngôn ngữ.

GIL (Global Interpreter Lock)

Trong những ngôn ngữ như Python hoặc Ruby, một GIL/GVL toàn cục có thể ngăn nhiều thread thực thi bytecode cùng lúc. Vì vậy, để đạt được parallelism thực sự trên CPU nhiều nhân, chúng ta thường dùng multi-process thay vì multi-thread.

Ưu điểm

  • Cô lập tài nguyên tốt hơn so với multi-thread.
  • Một process lỗi không kéo sập toàn bộ hệ thống.
  • Có thể tận dụng nhiều core CPU thật sự.
  • Phù hợp với các tác vụ CPU-bound.

Copy-on-Write (CoW)

Mặc dù mỗi process có bộ nhớ riêng, hệ điều hành hiện đại dùng một kỹ thuật gọi là Copy-on-Write. Khi một process được fork, OS không sao chép toàn bộ bộ nhớ ngay lập tức. Hai process sẽ cùng “dùng chung” các trang nhớ vật lý cho đến khi một bên ghi thay đổi. Chỉ lúc đó trang nhớ đó mới được sao chép thật sự. Điều này giúp multi-process hiệu quả hơn so với cảm giác “nặng” trên lý thuyết.

Trong PostgreSQL, khi fork một tiến trình mới (Process by Connection) dùng cơ chế này, mình đã giải thích trong bải đăng trước.

Nhược điểm

  • Tạo và quản lý process tốn chi phí hơn thread, vì việc sinh process cần những system call nặng hơn.
  • Giao tiếp giữa các process phức tạp hơn, vì các không gian nhớ tách biệt.
  • Tốn nhiều bộ nhớ hơn vì mỗi process vẫn có stack, heap và các thư viện riêng.

IPC complexity

Vì process không thể nhìn trực tiếp vào bộ nhớ của nhau, chúng phải dùng các cơ chế Inter-Process Communication (IPC):

  • Pipes & sockets: truyền dữ liệu theo luồng, giống như một cuộc gọi.
  • Shared memory: tạo một vùng nhớ chung để hai process cùng truy cập.
  • Message queues: để lại tin nhắn trong hàng đợi cho process khác đọc sau.

Kết phần này

Process và Thread là những khái niệm quan trọng mà anh em cần nắm rõ. Phần tiếp theo, chúng ta sẽ cùng tìm hiểu về tính đa nhiệm của CPU, cách mà CPU làm việc với nhiều software Thread cùng một lúc. Ngoài ra còn các cơ chế cache trong CPU, vì sao trong java lại có "volatile", rất hay, anh em đón đọc nhé.

Hệ điều hành (Phần 2): Tìm hiểu về Multitasking, Scheduler, Shared Memory và CPU Caches


Bài viết này cũng được mình dịch sang tiếng Anh trên blog substack của mình.

Mình viết lại những điều này như một cách để ghi nhớ hành trình làm nghề của mình. Nếu bạn cũng đang làm backend, devops hoặc cloud, hy vọng những chia sẻ này có thể giúp bạn một chút gì đó. Còn nếu có chỗ nào mình hiểu chưa đúng, mình vẫn luôn sẵn sàng học thêm.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.