PYTHON CONCURRENCY
CÁC CHỦ ĐỀ
BÀI MỚI NHẤT
MỚI CẬP NHẬT

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.

test php

banquyen png
Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

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:

  1. Đầ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 acquirerelease 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.

Cùng chuyên mục:

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn toàn diện về module datetime trong Python

Hướng dẫn toàn diện về module datetime trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Từ khóa yield trong Python

Từ khóa yield trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Định dạng chuỗi Strings trong Python

Định dạng chuỗi Strings trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Làm việc với các biến môi trường trong Python

Làm việc với các biến môi trường trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa iterator và iterable trong Python

Sự khác biệt giữa iterator và iterable trong Python

Cách làm việc với file tarball/tar trong Python

Cách làm việc với file tarball/tar trong Python

Chuyển đổi kiểu dữ liệu trong Python

Chuyển đổi kiểu dữ liệu trong Python

Sự khác biệt giữa toán tử == và is trong Python

Sự khác biệt giữa toán tử == và is trong Python

Làm việc với file ZIP trong Python

Làm việc với file ZIP trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Sự khác biệt giữa byte objects và string trong Python

Sự khác biệt giữa byte objects và string trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format  trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Top