Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Cách sử dụng Semaphore trong Python
Trong lập trình đa luồng, việc kiểm soát truy cập vào tài nguyên chia sẻ là một thách thức lớn. Nếu nhiều luồng cùng truy cập vào tài nguyên này một cách đồng thời mà không có cơ chế quản lý, sẽ dễ dẫn đến các vấn đề về đồng bộ hóa như điều kiện cạnh tranh (race conditions) và xung đột dữ liệu. Để giải quyết vấn đề này, Python cung cấp Semaphore - một công cụ đồng bộ hóa mạnh mẽ cho phép bạn giới hạn số lượng luồng có thể truy cập vào tài nguyên chia sẻ cùng lúc. Trong bài viết này, bạn sẽ học cách sử dụng Semaphore trong Python để đảm bảo rằng chỉ một số lượng nhất định các luồng có thể truy cập tài nguyên chia sẻ, giúp ngăn ngừa các vấn đề về đồng bộ hóa và đảm bảo hoạt động ổn định của chương trình.
Giới thiệu về Semaphore trong Python
Semaphore trong Python là một công cụ đồng bộ hóa cho phép bạn kiểm soát việc truy cập vào một tài nguyên chia sẻ. Cơ bản, một semaphore là một bộ đếm liên kết với một khóa, giới hạn số lượng luồng có thể truy cập vào tài nguyên chia sẻ cùng lúc.
Semaphore giúp ngăn chặn các vấn đề đồng bộ hóa luồng như điều kiện cạnh tranh (race conditions), khi nhiều luồng cố gắng truy cập tài nguyên cùng lúc và làm nhiễu loạn hoạt động của nhau.
Semaphore duy trì một bộ đếm. Khi một luồng muốn truy cập tài nguyên chia sẻ, semaphore kiểm tra bộ đếm.
Bài viết này được đăng tại [free tuts .net]
Nếu bộ đếm lớn hơn không, nó giảm bộ đếm và cho phép luồng truy cập tài nguyên. Nếu bộ đếm bằng không, semaphore sẽ chặn luồng cho đến khi bộ đếm lớn hơn không.
Semaphore có hai thao tác chính:
- Acquire: Thao tác acquire kiểm tra bộ đếm và giảm nó nếu lớn hơn không. Nếu bộ đếm bằng không, semaphore sẽ chặn luồng cho đến khi một luồng khác giải phóng semaphore.
- Release: Thao tác release tăng bộ đếm, cho phép các luồng khác có thể acquire nó.
Sử dụng Semaphore trong Python
Để sử dụng semaphore, bạn làm theo các bước sau:
-
Đầu tiên, import module threading:
import threading
Thứ hai, tạo một đối tượng Semaphore và chỉ định số lượng luồng có thể acquire nó cùng lúc:
semaphore = threading.Semaphore(3)
-
Trong ví dụ này, chúng ta tạo một đối tượng Semaphore chỉ cho phép tối đa ba luồng acquire nó cùng lúc.
-
Thứ ba, acquire semaphore từ một luồng bằng cách gọi phương thức
acquire()
:
semaphore.acquire()
-
Nếu bộ đếm semaphore bằng không, luồng sẽ đợi cho đến khi một luồng khác giải phóng semaphore. Sau khi acquire semaphore, bạn có thể thực thi một đoạn mã quan trọng.
-
Cuối cùng, giải phóng semaphore sau khi chạy đoạn mã quan trọng bằng cách gọi phương thức
release()
:
semaphore.release()
Để đảm bảo semaphore được acquire và release một cách chính xác, ngay cả khi xảy ra ngoại lệ trong khi chạy đoạn mã quan trọng, bạn có thể sử dụng câu lệnh with
:
with semaphore: # Code trong khối này đã acquire semaphore # Thực hiện các thao tác trên tài nguyên chia sẻ # ... # Semaphore được giải phóng bên ngoài khối with
Câu lệnh with
sẽ acquire và release semaphore tự động, làm cho mã của bạn ít dễ bị lỗi hơn.
Ví dụ về Semaphore trong Python
Ví dụ sau đây minh họa cách sử dụng semaphore để giới hạn số lượng tải xuống đồng thời tối đa là ba bằng cách sử dụng multithreading trong Python:
import threading import urllib.request MAX_CONCURRENT_DOWNLOADS = 3 semaphore = threading.Semaphore(MAX_CONCURRENT_DOWNLOADS) def download(url): with semaphore: print(f"Đang tải xuống {url}...") response = urllib.request.urlopen(url) data = response.read() print(f"Đã tải xong {url}") return data def main(): # Các URL để tải xuống urls = [ 'https://www.ietf.org/rfc/rfc791.txt', 'https://www.ietf.org/rfc/rfc792.txt', 'https://www.ietf.org/rfc/rfc793.txt', 'https://www.ietf.org/rfc/rfc794.txt', 'https://www.ietf.org/rfc/rfc795.txt', ] # Tạo các luồng cho mỗi lượt tải xuống threads = [] for url in urls: thread = threading.Thread(target=download, args=(url,)) threads.append(thread) thread.start() # Đợi tất cả các luồng hoàn thành for thread in threads: thread.join() if __name__ == '__main__': main()
Kết quả
Đang tải xuống https://www.ietf.org/rfc/rfc791.txt... Đang tải xuống https://www.ietf.org/rfc/rfc792.txt... Đang tải xuống https://www.ietf.org/rfc/rfc793.txt... Đã tải xong https://www.ietf.org/rfc/rfc792.txt Đang tải xuống https://www.ietf.org/rfc/rfc794.txt... Đã tải xong https://www.ietf.org/rfc/rfc791.txt Đang tải xuống https://www.ietf.org/rfc/rfc795.txt... Đã tải xong https://www.ietf.org/rfc/rfc793.txt Đã tải xong https://www.ietf.org/rfc/rfc794.txt Đã tải xong https://www.ietf.org/rfc/rfc795.txt
Giải thích chi tiết
Đầu tiên, import module threading và urllib.request:
import threading import urllib.request
Thứ hai, tạo một đối tượng Semaphore để kiểm soát số lượng luồng có thể tải xuống đồng thời tối đa là ba:
MAX_CONCURRENT_DOWNLOADS = 3 semaphore = threading.Semaphore(MAX_CONCURRENT_DOWNLOADS)
Thứ ba, định nghĩa hàm download()
để tải xuống từ một URL. Hàm download
acquire và release semaphore bằng cách sử dụng câu lệnh with
. Nó cũng sử dụng module urllib.request
để tải dữ liệu từ một URL:
def download(url): with semaphore: print(f"Đang tải xuống {url}...") response = urllib.request.urlopen(url) data = response.read() print(f"Đã tải xong {url}") return data
Thứ tư, định nghĩa hàm main()
để tạo năm luồng dựa trên danh sách URL và bắt đầu chúng để tải dữ liệu:
def main(): # Các URL để tải xuống urls = [ 'https://www.ietf.org/rfc/rfc791.txt', 'https://www.ietf.org/rfc/rfc792.txt', 'https://www.ietf.org/rfc/rfc793.txt', 'https://www.ietf.org/rfc/rfc794.txt', 'https://www.ietf.org/rfc/rfc795.txt', ] # Tạo các luồng cho mỗi lượt tải xuống threads = [] for url in urls: thread = threading.Thread(target=download, args=(url,)) threads.append(thread) thread.start() # Đợi tất cả các luồng hoàn thành for thread in threads: thread.join()
Cuối cùng, gọi hàm main()
trong phần if __name__ == '__main__':
if __name__ == '__main__': main()
Kết bài
Hy vọng rằng bài viết này đã giúp bạn hiểu rõ cách sử dụng semaphore trong Python để kiểm soát số lượng luồng có thể truy cập tài nguyên chia sẻ cùng lúc. Việc sử dụng phương thức acquire()
để kiểm tra và giảm bộ đếm của semaphore, cũng như phương thức release()
để tăng bộ đếm, là rất quan trọng để đảm bảo đồng bộ hóa. Bằng cách sử dụng câu lệnh with
, bạn có thể tự động và an toàn acquire
và release
semaphore, giúp mã nguồn của bạn trở nên gọn gàng và ít lỗi hơn. Sử dụng semaphore một cách hiệu quả sẽ giúp bạn tránh được các vấn đề về điều kiện cạnh tranh và xung đột dữ liệu, đảm bảo hoạt động ổn định và hiệu quả cho các ứng dụng đa luồng.