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.

Tìm hiểu về điều kiện race của threading Lock trong Python

Trong lập trình đa luồng, việc nhiều luồng cùng truy cập và thay đổi một biến chia sẻ có thể dẫn đến các kết quả không mong muốn và không thể đoán trước, gây ra hiện tượng gọi là "điều kiện race". Đây là một vấn đề phổ biến và tiềm ẩn nhiều nguy cơ trong các ứng dụng đòi hỏi tính đồng bộ và chính xác cao. Để giải quyết vấn đề này, Python cung cấp một công cụ hữu ích là threading.Lock. Bài viết này sẽ giúp bạn hiểu rõ hơn về điều kiện race, cách sử dụng đối tượng Lock để ngăn chặn chúng, và minh họa bằng các ví dụ cụ thể để bạn có thể áp dụng vào thực tế lập trình của mì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.

Điều kiện race là gì?

Điều kiện race xảy ra khi hai hoặc nhiều luồng cùng truy cập một biến chia sẻ đồng thời, dẫn đến kết quả không thể đoán trước.

Trong tình huống này, luồng đầu tiên đọc giá trị từ biến chia sẻ. Cùng lúc đó, luồng thứ hai cũng đọc giá trị từ biến chia sẻ đó.

Sau đó, cả hai luồng cố gắng thay đổi giá trị của biến chia sẻ. Vì các cập nhật xảy ra đồng thời, nên sẽ tạo ra một cuộc "đua" để xác định thay đổi của luồng nào sẽ được lưu lại.

Bài viết này được đăng tại [free tuts .net]

Giá trị cuối cùng của biến chia sẻ phụ thuộc vào luồng nào hoàn thành việc cập nhật cuối cùng. Luồng nào thay đổi giá trị cuối cùng sẽ "thắng cuộc đua".

Ví dụ về điều kiện race

Ví dụ sau minh họa một điều kiện race:

from threading import Thread
from time import sleep

counter = 0

def increase(by):
    global counter

    local_counter = counter
    local_counter += by

    sleep(0.1)

    counter = local_counter
    print(f'counter={counter}')

# tạo các luồng
t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))

# bắt đầu các luồng
t1.start()
t2.start()

# đợi các luồng hoàn thành
t1.join()
t2.join()

print(f'Giá trị cuối cùng của counter là {counter}')

Trong chương trình này, cả hai luồng cố gắng thay đổi giá trị của biến counter cùng lúc. Giá trị của biến counter phụ thuộc vào luồng nào hoàn thành cuối cùng.

Nếu luồng t1 hoàn thành trước luồng t2, bạn sẽ thấy kết quả sau:

counter=10
counter=20
Giá trị cuối cùng của counter là 20

Ngược lại, bạn sẽ thấy kết quả sau:

counter=20
counter=10
Giá trị cuối cùng của counter là 10

Sử dụng threading lock để ngăn chặn điều kiện race trong Python

Để ngăn chặn điều kiện race, bạn có thể sử dụng một khóa luồng (threading lock).

Khóa luồng là một nguyên thủy đồng bộ cung cấp quyền truy cập độc quyền vào tài nguyên chia sẻ trong một ứng dụng đa luồng. Khóa luồng cũng được gọi là mutex, viết tắt của "mutual exclusion" (loại trừ lẫn nhau).

Thông thường, một khóa luồng có hai trạng thái: khóa và mở khóa. Khi một luồng chiếm khóa, khóa chuyển sang trạng thái khóa. Luồng này có thể truy cập độc quyền vào tài nguyên chia sẻ.

Các luồng khác cố gắng chiếm khóa trong khi khóa đang bị khóa sẽ bị chặn và phải đợi cho đến khi khóa được giải phóng.

Trong Python, bạn có thể sử dụng lớp Lock từ mô-đun threading để tạo một đối tượng khóa:

Đầu tiên, tạo một thể hiện của lớp Lock:

lock = Lock()

Mặc định, khóa được mở khóa cho đến khi một luồng chiếm nó.

Thứ hai, chiếm khóa bằng cách gọi phương thức acquire():

lock.acquire()

Thứ ba, giải phóng khóa khi luồng hoàn thành việc thay đổi biến chia sẻ:

lock.release()

Ví dụ sau cho thấy cách sử dụng đối tượng Lock để ngăn chặn điều kiện race trong chương trình trước:

from threading import Thread, Lock
from time import sleep

counter = 0

def increase(by, lock):
    global counter

    lock.acquire()

    local_counter = counter
    local_counter += by

    sleep(0.1)

    counter = local_counter
    print(f'counter={counter}')

    lock.release()

lock = Lock()

# tạo các luồng
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))

# bắt đầu các luồng
t1.start()
t2.start()

# đợi các luồng hoàn thành
t1.join()
t2.join()

print(f'Giá trị cuối cùng của counter là {counter}')

Kết quả đầu ra:

counter=10
counter=30
Giá trị cuối cùng của counter là 30

Sử dụng khóa luồng với câu lệnh with trong Python

Dễ dàng hơn để sử dụng đối tượng khóa với câu lệnh with để chiếm và giải phóng khóa trong một khối mã:

import threading

# Tạo đối tượng khóa
lock = threading.Lock()

# Thực hiện một số thao tác trong vùng quan trọng
with lock:
    # Khóa đã được chiếm trong khối with
    # Thực hiện các thao tác trên tài nguyên chia sẻ
    # ...

# khóa được giải phóng ngoài khối with

Ví dụ, bạn có thể sử dụng câu lệnh with mà không cần gọi các phương thức acquire()release() trong ví dụ trên như sau:

from threading import Thread, Lock
from time import sleep

counter = 0

def increase(by, lock):
    global counter

    with lock:
        local_counter = counter
        local_counter += by

        sleep(0.1)

        counter = local_counter
        print(f'counter={counter}')

lock = Lock()

# tạo các luồng
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))

# bắt đầu các luồng
t1.start()
t2.start()

# đợi các luồng hoàn thành
t1.join()
t2.join()

print(f'Giá trị cuối cùng của counter là {counter}')

Định nghĩa lớp Counter an toàn với luồng sử dụng đối tượng Lock trong Python

Ví dụ sau minh họa cách định nghĩa một lớp Counter an toàn với luồng sử dụng đối tượng Lock:

from threading import Thread, Lock
from time import sleep

class Counter:
    def __init__(self):
        self.value = 0
        self.lock = Lock()

    def increase(self, by):
        with self.lock:
            current_value = self.value
            current_value += by

            sleep(0.1)

            self.value = current_value
            print(f'counter={self.value}')

def main():
    counter = Counter()
    # tạo các luồng
    t1 = Thread(target=counter.increase, args=(10,))
    t2 = Thread(target=counter.increase, args=(20,))

    # bắt đầu các luồng
    t1.start()
    t2.start()

    # đợi các luồng hoàn thành
    t1.join()
    t2.join()

    print(f'Giá trị cuối cùng của counter là {counter.value}')

if __name__ == '__main__':
    main()

Kết bài

Điều kiện race là một vấn đề thường gặp khi nhiều luồng cùng truy cập và thay đổi một biến chia sẻ đồng thời. Để ngăn chặn điều này, việc sử dụng đối tượng khóa luồng (threading.Lock) là rất cần thiết. Bằng cách sử dụng phương thức acquire() để chiếm khóa và release() để giải phóng khóa, ta có thể đảm bảo rằng chỉ có một luồng được truy cập và thay đổi biến chia sẻ tại một thời điểm. Hơn nữa, sử dụng đối tượng khóa với câu lệnh with giúp việc quản lý khóa trở nên đơn giản và hiệu quả hơn. Nhờ đó, bạn có thể viết các chương trình đa luồng an toàn và chính xác, giảm thiểu rủi ro và tăng hiệu suất của ứng dụ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