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ả.
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:
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, để 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;
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ì đó
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ữ 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
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;
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.