Python - Động Trùng

Xin chào, các nhà lập trình mong muốn! Hôm nay, chúng ta sẽ bơi lội vào thế giới thú vị của các luồng Python và khám phá một lỗi phổ biến có tên là động trùng. Đừng lo nếu bạn mới bắt đầu học lập trình; tôi sẽ hướng dẫn bạn qua khái niệm này bước từng bước, như cách tôi đã làm cho hàng ngàn học viên trong những năm dạy học. Vậy, hãy lấy ly cà phê yêu thích của bạn, và hãy cùng nhau bắt đầu chuyến phiêu lưu thú vị này!

Python - Thread Deadlock

Động Trùng Là Gì?

Trước khi bước vào những chi tiết của các luồng Python, hãy hiểu rõ động trùng là gì. Hãy tưởng tượng bạn đang ở trong một hành lang tròn với người bạn của bạn. Cả hai bạn đều cầm một chiếc hộp lớn, và để đối xử qua nhau, một trong hai bạn cần để xuống hộp. Nhưng có một điều kiện: cả hai bạn đều quyết định sẽ không để xuống hộp cho đến khi người kia làm điều đó. Bây giờ bạn bị kẹt! Đó chính là điều gì động trùng trong lập trình - khi hai hoặc nhiều luồng đang chờ nhau để giải phóng tài nguyên, và không một trong số đó có thể tiếp tục.

Làm Thế Nào để Tránh Động Trùng Trong Các Luồng Python

Bây giờ khi chúng ta hiểu rõ động trùng là gì, hãy xem cách chúng ta có thể tránh chúng trong Python. Có một số chiến lược chúng ta có thể sử dụng:

1. Thứ Tự Khóa

Một trong những cách đơn giản nhất để tránh động trùng là luôn thu thập các khóa theo thứ tự nhất quán. Hãy xem một ví dụ:

import threading

lock1 = threading.Lock()
lock2 = threading.Lock()

def worker1():
with lock1:
print("Worker1 đã thu thập lock1")
with lock2:
print("Worker1 đã thu thập lock2")
# Thực hiện một số công việc

def worker2():
with lock1:
print("Worker2 đã thu thập lock1")
with lock2:
print("Worker2 đã thu thập lock2")
# Thực hiện một số công việc

t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)

t1.start()
t2.start()
t1.join()
t2.join()

Trong ví dụ này, cả worker1 và worker2 đều thu thập lock1 trước, sau đó là lock2. Thứ tự nhất quán này ngăn chặn động trùng.

2. Cơ Chế Thời Gian Chờ

Chiến lược khác là sử dụng thời gian chờ khi thu thập các khóa. Nếu một luồng không thể thu thập khóa trong một khoảng thời gian nhất định, nó sẽ từ bỏ và thử lại sau. Dưới đây là cách bạn có thể thực hiện điều này:

import threading
import time

lock = threading.Lock()

def worker(id):
while True:
if lock.acquire(timeout=1):
try:
print(f"Worker {id} đã thu thập khóa")
time.sleep(2)  # Mô phỏng một số công việc
finally:
lock.release()
print(f"Worker {id} đã giải phóng khóa")
else:
print(f"Worker {id} không thể thu thập khóa, thử lại...")
time.sleep(0.5)  # Chờ trước khi thử lại

t1 = threading.Thread(target=worker, args=(1,))
t2 = threading.Thread(target=worker, args=(2,))

t1.start()
t2.start()

Trong ví dụ này, nếu một worker không thể thu thập khóa trong 1 giây, nó sẽ in ra một thông báo và thử lại sau một khoảng thời gian ngắn.

Cơ Chế Khóa Với Đối Tượng Lock

Đối tượng Lock trong Python là một công cụ cơ bản cho sự đồng bộ hóa giữa các luồng. Nó giống như một chìa khóa mà chỉ một luồng có thể giữ tại một thời điểm. Hãy xem cách sử dụng nó:

import threading
import time

counter = 0
lock = threading.Lock()

def increment():
global counter
with lock:
current = counter
time.sleep(0.1)  # Mô phỏng một số công việc
counter = current + 1

threads = []
for i in range(10):
t = threading.Thread(target=increment)
threads.append(t)
t.start()

for t in threads:
t.join()

print(f"Giá trị cuối cùng của biến đếm: {counter}")

Trong ví dụ này, chúng ta sử dụng khóa để đảm bảo rằng chỉ một luồng có thể sửa đổi biến đếm tại một thời điểm. Câu lệnh with tự động thu thập và giải phóng khóa.

Đối Tượng Semaphore cho Đồng Bộ Hóa

Semaphore giống như một nhân viên bảo vệ tại một câu lạc bộ chỉ cho phép một số ít người vào tại một thời điểm. Nó rất hữu ích khi bạn muốn hạn chế truy cập vào một tài nguyên. Dưới đây là cách bạn có thể sử dụng nó:

import threading
import time

semaphore = threading.Semaphore(2)  # Cho phép tối đa 2 luồng cùng lúc

def worker(id):
with semaphore:
print(f"Worker {id} đang làm việc")
time.sleep(2)  # Mô phỏng một số công việc
print(f"Worker {id} đã hoàn thành")

threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()

for t in threads:
t.join()

Trong ví dụ này, mặc dù chúng ta tạo 5 luồng, chỉ có 2 luồng có thể "làm việc" đồng thời do Semaphore.

Kết Luận

Xin chúc mừng! Bạn đã bước ra những bước đầu tiên vào thế giới các luồng Python và học cách tránh được động trùng kỳ lạnh. Nhớ rằng, như học lái xe đạp, việc nắm vững luồng cần nhiều tập luyện. Đừng bỏ cuộc nếu điều đó không diễn ra ngay lập tức - tiếp tục lập trình, tiếp tục thử nghiệm, và sớm bạn sẽ threading như một chuyên gia!

Dưới đây là tóm tắt các phương pháp chúng ta đã thảo luận:

Phương Pháp Mô Tả
Thứ Tự Khóa Thu thập các khóa theo thứ tự nhất quán
Thời Gian Chờ Sử dụng thời gian chờ khi thu thập các khóa
Đối Tượng Lock Công cụ đồng bộ hóa cơ bản
Semaphore Hạn chế truy cập vào một tài nguyên

Giữ các công cụ này trong túi công cụ lập trình của bạn, và bạn sẽ được trang bị tốt để đối mặt với thách thức lập trình đồng thời. Chúc bạn thành công, các nhà lập trình Python tương lai!

Credits: Image by storyset