Trong lập trình đa tiến trình (multiprocessing), việc hiểu rõ các khái niệm cơ bản và cơ chế đồng bộ hóa là yếu tố then chốt để xây dựng hệ thống hiệu năng cao và tin cậy.
1. Các khái niệm cốt lõi
- Parallel (Song song): Nhiều tiến trình thực thi thực sự tại cùng một thời điểm (thường trên CPU đa nhân).
- Concurrency (Đồng thời): Nhiều tiến trình luân phiên thực hiện trong một khoảng thời gian, tạo cảm giác chúng chạy cùng lúc.
- Synchronous (Đồng bộ): Tiến trình hiện tại phải chờ kết quả từ một tiến trình hoặc tác vụ khác mới được tiếp tục.
- Asynchronous (Bất đồng bộ): Tiến trình có thể tiếp tục thực thi mà không cần chờ đợi kết quả ngay lập tức từ tác vụ khác.
- Blocking (Chặn): Chương trình tạm dừng thực thi do chờ đợi một sự kiện (như I/O hoặc tài nguyên bị khóa).
- Non-blocking (Không chặn): Chương trình không dừng lại khi gặp thao tác I/O; nếu dữ liệu chưa sẵn sàng, nó sẽ bỏ qua hoặc trả về lỗi ngay lập tức.
2. Thao tác với Process trong Python
Thư viện multiprocessing cung cấp lớp Process để khởi tạo và quản lý tiến trình với các phương thức quan trọng:
start(): Kích hoạt tiến trình con.join(): Chuyển từ bất đồng bộ sang đồng bộ, khiến tiến trình cha phải chờ tiến trình con hoàn thành.is_alive(): Kiểm tra trạng thái hoạt động của tiến trình.terminate(): Cưỡng ép dừng tiến trình.daemon: Thuộc tính thiết lập tiến trình hộ vệ (daemon process). Tiến trình này sẽ tự động kết thúc khi tiến trình cha kết thúc.
from multiprocessing import Process
import time
def task_worker(name):
print(f"Bắt đầu tác vụ: {name}")
time.sleep(2)
print(f"Hoàn thành tác vụ: {name}")
if __name__ == '__main__':
p = Process(target=task_worker, args=("Worker-01",))
p.start()
# Nếu không có p.join(), tiến trình chính sẽ chạy tiếp ngay lập tức
p.join()
print("Tiến trình chính đã kết thúc.")
3. Chia sẻ dữ liệu và vấn đề Race Condition
Mặc dù các tiến trình có không gian bộ nhớ độc lập, đôi khi chúng ta cần chia sẻ dữ liệu thông qua Value hoặc Array. Tuy nhiên, việc nhiều tiến trình cùng ghi vào một vùng nhớ sẽ gây ra lỗi dữ liệu (Race Condition).
from multiprocessing import Process, Value, Lock
import time
def modify_balance(val, lock, delta):
for _ in range(50):
with lock: # Sử dụng Lock để đảm bảo an toàn luồng
current = val.value
time.sleep(0.01)
val.value = current + delta
if __name__ == '__main__':
balance = Value('i', 1000)
mu_lock = Lock()
p1 = Process(target=modify_balance, args=(balance, mu_lock, 10))
p2 = Process(target=modify_balance, args=(balance, mu_lock, -10))
p1.start()
p2.start()
p1.join()
p2.join()
print(f"Số dư cuối cùng: {balance.value}")
4. Cơ chế Semaphore (Tín hiệu hiệu số)
Semaphore hoạt động như một tập hợp các giấy phép. Nó cho phép một số lượng giới hạn các tiến trình cùng truy cập vào một tài nguyên cụ thể, thay vì chỉ một như Lock.
from multiprocessing import Process, Semaphore
import time, random
def access_resource(sem, user_id):
sem.acquire()
print(f"Người dùng {user_id} đang sử dụng tài nguyên.")
time.sleep(random.uniform(1, 3))
print(f"Người dùng {user_id} đã giải phóng tài nguyên.")
sem.release()
if __name__ == '__main__':
# Chỉ cho phép tối đa 3 tiến trình truy cập cùng lúc
pool_limit = Semaphore(3)
for i in range(10):
Process(target=access_resource, args=(pool_limit, i)).start()
5. Cơ chế Event (Sự kiện)
Event là một trong những cơ chế giao tiếp đơn giản nhất giữa các tiến trình. Một tiến trình phát ra tín hiệu (set), và các tiến trình khác sẽ chờ tín hiệu đó (wait).
set(): Chuyển trạng thái cờ sang True (mở chặn).clear(): Chuyển trạng thái cờ sang False (thiết lập chặn).wait(): Chờ cho đến khi cờ là True.
from multiprocessing import Process, Event
import time
def traffic_light(event):
while True:
print("--- Đèn ĐỎ ---")
event.clear()
time.sleep(3)
print("--- Đèn XANH ---")
event.set()
time.sleep(3)
def vehicle(event, car_id):
if not event.is_set():
print(f"Xe {car_id} đang chờ đèn...")
event.wait()
print(f"Xe {car_id} đã đi qua.")
if __name__ == '__main__':
e = Event()
Process(target=traffic_light, args=(e,), daemon=True).start()
for i in range(5):
time.sleep(1)
Process(target=vehicle, args=(e, i)).start()
6. Mô hình Producer-Consumer
Đây là mô hình giúp giải quyết vấn đề chênh lệch tốc độ giữa bên tạo dữ liệu (Producer) và bên xử lý dữ liệu (Consumer), giúp hệ thống đạt được sự "lỏng lẻo" (decoupling) trong kiến trúc.