Cách Tránh Deadlock và Giữ Chương Trình Chạy Mượt Mà

Gặp khó khăn với deadlock trong các ứng dụng đa luồng của bạn? Hướng dẫn này cung cấp một giải thích rõ ràng về deadlock và các chiến lược thực tiễn để giảm thiểu và xử lý chúng, đảm bảo chương trình của bạn chạy hiệu quả.
Cách Tránh Deadlock và Giữ Chương Trình Chạy Mượt Mà

Cách deadlock xảy ra, tác động của chúng và các kỹ thuật khác nhau để ngăn chặn và quản lý chúng. Bạn sẽ học về cách lấy tài nguyên theo một thứ tự cụ thể, sử dụng timeouts, tránh phụ thuộc vòng tròn, và tận dụng các cơ chế phát hiện deadlock. Ngoài ra, bài viết còn khám phá các phương pháp giảm thời gian khóa và chọn mức độ cô lập phù hợp để giảm thiểu xung đột. Cuối cùng, nó làm nổi bật các công cụ phân tích deadlock có sẵn trong các cơ sở dữ liệu phổ biến như MySQL và Postgres. Bằng cách hiểu về deadlock, lập trình viên có thể tạo ra các ứng dụng đa luồng mạnh mẽ và hiệu quả.

Deadlock là gì?

Hãy tưởng tượng bạn đang ở trong một canteen của trường học. Bạn cần một cái nĩa và một con dao để ăn trưa. Bạn của bạn cũng cần cả hai dụng cụ này. Trên bàn chỉ có một cái nĩa và một con dao.
Bạn lấy cái nĩa, và bạn của bạn lấy con dao. Bây giờ cả hai đều bị kẹt! Bạn không thể ăn mà không có con dao, và bạn của bạn không thể ăn mà không có cái nĩa. Không ai trong số các bạn muốn từ bỏ dụng cụ của mình, hy vọng người kia sẽ chia sẻ trước. Tình huống này, nơi mọi người đều chờ đợi người khác, được gọi là deadlock.

Trong lập trình máy tính, deadlock xảy ra trong multithreading khi:

  • Nhiều thread (như bạn và bạn của bạn) cần nhiều tài nguyên (như cái nĩa và con dao).
  • Mỗi thread giữ một tài nguyên và chờ đợi tài nguyên khác.
  • Không có thread nào sẵn sàng từ bỏ tài nguyên của mình.
  • Tất cả các thread đều bị kẹt, chờ đợi mãi mãi.

Để ngăn chặn deadlock

Theo cùng thứ tự

Triển khai một hệ thống nơi các tài nguyên luôn được yêu cầu theo cùng một thứ tự.

Ví dụ: Trong một ứng dụng ngân hàng, giả sử chúng ta có hai tài khoản: A và B. Để chuyển tiền, chúng ta cần khóa cả hai tài khoản. Để ngăn chặn deadlock, chúng ta luôn khóa tài khoản có số tài khoản nhỏ hơn trước.

def transfer(from_account, to_account, amount):
    first_lock = min(from_account, to_account)
    second_lock = max(from_account, to_account)
    
    with lock(first_lock):
        with lock(second_lock):
            # Perform the transfer
            pass

Sử dụng timeouts

Sử dụng timeouts, để các thread không chờ đợi mãi mãi. Ví dụ: Khi cố gắng lấy một khóa, chúng ta đặt một thời gian chờ tối đa. Nếu khóa không được lấy trong thời gian này, thread sẽ từ bỏ và thử lại sau.

SET LOCK_TIMEOUT 5000; -- Đặt thời gian chờ là 5 giây
BEGIN TRANSACTION;
-- Các câu lệnh SQL của bạn ở đây
COMMIT;

Tránh phụ thuộc vòng tròn

Thiết kế chương trình của bạn để tránh phụ thuộc vòng tròn. Ví dụ: Thay vì để Thread A chờ Thread B trong khi Thread B chờ Thread A, hãy thiết kế lại để chỉ một thread chờ thread kia.

# Tránh làm như thế này:
def thread_a():
    with lock_a:
        # làm gì đó
        with lock_b:
            # làm gì khác

def thread_b():
    with lock_b:
        # làm gì đó
        with lock_a:
            # làm gì khác

# Làm như thế này thay thế:
def thread_a():
    with lock_a:
        # làm gì đó
        # Báo hiệu cho thread_b tiếp tục

def thread_b():
    # Chờ tín hiệu từ thread_a
    with lock_b:
        # làm gì đó

Phát hiện deadlock

Nhiều cơ sở dữ liệu có cơ chế phát hiện deadlock tích hợp sẵn. Khi được phát hiện, một giao dịch được chọn làm "nạn nhân" (victim) và bị rollback.

Giảm thời gian khóa

Giữ các giao dịch ngắn nhất có thể để giảm thiểu khả năng xung đột.

-- Thay vì làm như thế này:
BEGIN TRANSACTION;
-- Hoạt động dài hạn
UPDATE LargeTable SET column = value;
COMMIT;

-- Hãy làm như thế này:
BEGIN TRANSACTION;
UPDATE LargeTable SET column = value WHERE id BETWEEN 1 AND 1000;
COMMIT;
-- Lặp lại cho các phạm vi khác

Sử dụng mức độ cô lập phù hợp

Chọn mức độ cô lập phù hợp với nhu cầu của bạn. Mức độ cô lập thấp hơn có thể giảm sự tranh chấp khóa.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
-- Các câu lệnh SQL của bạn ở đây
COMMIT;

Phân tích deadlock

Nhiều cơ sở dữ liệu cung cấp công cụ để phân tích deadlock. Sử dụng những công cụ này để xác định và khắc phục các mẫu truy vấn có vấn đề.
Ví dụ:

Hiểu về deadlock giúp lập trình viên tạo ra phần mềm hiệu quả và đáng tin cậy hơn.