Bế tắc là gì?


159

Khi viết các ứng dụng đa luồng, một trong những vấn đề phổ biến nhất gặp phải là bế tắc.

Câu hỏi của tôi cho cộng đồng là:

  1. Bế tắc là gì?

  2. Làm thế nào để bạn phát hiện ra chúng?

  3. Bạn có xử lý chúng?

  4. Và cuối cùng, làm thế nào để bạn ngăn chặn chúng xảy ra?


Câu trả lời:


206

Một khóa xảy ra khi nhiều quy trình cố gắng truy cập vào các tài nguyên tương tự cùng một lúc.

Một quá trình mất đi và phải chờ cho quá trình khác kết thúc.

Một sự bế tắc xảy ra khi quá trình chờ đợi vẫn đang giữ một tài nguyên khác mà nhu cầu đầu tiên trước khi nó có thể kết thúc.

Vì vậy, một ví dụ:

Tài nguyên A và tài nguyên B được sử dụng bởi quy trình X và quy trình Y

  • X bắt đầu sử dụng A.
  • X và Y cố gắng bắt đầu sử dụng B
  • Y 'thắng' và được B trước
  • Bây giờ Y cần sử dụng A
  • A bị khóa bởi X, đang chờ Y

Cách tốt nhất để tránh bế tắc là tránh các quy trình được xử lý theo cách này. Giảm nhu cầu khóa bất cứ thứ gì có thể.

Trong cơ sở dữ liệu tránh thực hiện nhiều thay đổi cho các bảng khác nhau trong một giao dịch, tránh kích hoạt và chuyển sang đọc lạc quan / bẩn / nolock càng nhiều càng tốt.


9
Tôi đang sử dụng quy trình ở đây để khái quát hóa, không cụ thể là Quy trình HĐH. Đây có thể là các luồng, nhưng cũng có thể là các ứng dụng hoàn toàn khác nhau hoặc các kết nối cơ sở dữ liệu. Các mô hình là như nhau.
Keith

1
Xin chào, đưa ra kịch bản này: Chủ đề A khóa tài nguyên A và có một quá trình dài. Chủ đề B đang chờ khóa tài nguyên A. Thời gian sử dụng CPU: 20%, bạn có thể xem đó là tình huống bế tắc không?
rickyProgrammer

2
@rickyProgrammer không, đó chỉ là một khóa chờ thông thường, mặc dù sự khác biệt là một chút học thuật. B chờ trên chậm A là khóa, B chờ A đợi B là bế tắc.
Keith

Vì vậy, bế tắc là nhiều hơn hai quá trình với các tài nguyên bị khóa đang chờ các tài nguyên đó được phát hành ..
rickyProgrammer

2
@rickyProgrammer đó là một khóa sẽ không trở nên miễn phí, bất kể bạn chờ bao lâu, vì hàng đợi tròn.
Keith

126

Hãy để tôi giải thích một ví dụ thế giới thực (không thực tế) cho một tình huống bế tắc từ các bộ phim tội phạm. Hãy tưởng tượng một tên tội phạm giữ một con tin và chống lại điều đó, một cảnh sát cũng giữ một con tin là bạn của tên tội phạm. Trong trường hợp này, tên tội phạm sẽ không để con tin ra đi nếu cảnh sát không để bạn mình buông tay. Ngoài ra cảnh sát sẽ không để cho người bạn của tội phạm buông tay, trừ khi tên tội phạm thả con tin. Đây là một tình huống không đáng tin vô tận, bởi vì cả hai bên đều khăng khăng bước đầu tiên từ nhau.

Hình sự & Cảnh Cop

nhập mô tả hình ảnh ở đây

Vì vậy, đơn giản, khi hai luồng cần hai tài nguyên khác nhau và mỗi tài nguyên có khóa tài nguyên mà bên kia cần, đó là một bế tắc.

Một giải thích cấp cao khác về sự bế tắc: Trái tim tan vỡ

Bạn đang hẹn hò với một cô gái và một ngày sau cuộc cãi vã, cả hai bên đều đau lòng với nhau và chờ đợi một cuộc gọi tôi-xin lỗi-và-tôi-nhớ-bạn . Trong tình huống này, cả hai bên đều muốn liên lạc với nhau khi và chỉ khi một trong hai người nhận được cuộc gọi tôi xin lỗi từ bên kia. Bởi vì cả hai sẽ không bắt đầu giao tiếp và chờ đợi trong trạng thái thụ động, cả hai sẽ chờ đợi người kia bắt đầu giao tiếp, kết thúc trong tình huống bế tắc.


Shoudnt các chủ đề thuộc về các quy trình khác nhau?, Các chủ đề thuộc cùng một quy trình cũng có thể gây ra bế tắc?
chúa tể

1
@diabolicfreak Không thành vấn đề nếu các chủ đề có thuộc cùng một quy trình hay không.
Sam Malayek

2
Một ví dụ khác từ cuộc sống thực có thể là bốn chiếc ô tô đến giao nhau của hai con đường bằng nhau theo bốn hướng cùng một lúc. Mỗi người cần đưa ra một cách để một chiếc xe từ phía bên tay phải, vì vậy không ai có thể tiến hành.
LoBo

35

Bế tắc sẽ chỉ xảy ra khi bạn có hai hoặc nhiều ổ khóa có thể bị chìm cùng một lúc và chúng được lấy theo thứ tự khác nhau.

Các cách để tránh bị bế tắc là:

  • tránh có ổ khóa (nếu có thể),
  • tránh có nhiều hơn một khóa
  • luôn luôn lấy các ổ khóa theo cùng một thứ tự.

Điểm thứ 3 để ngăn chặn sự bế tắc (luôn luôn thực hiện các khóa theo cùng một thứ tự) là rất quan trọng, điều này khá dễ bị lãng quên trong thực tế mã hóa.
Qiang Xu

20

Để xác định bế tắc, đầu tiên tôi sẽ xác định quá trình.

Quá trình : Như chúng ta biết quá trình không có gì ngoài một programthực thi.

Tài nguyên : Để thực hiện một quy trình chương trình cần một số tài nguyên. Các loại tài nguyên có thể bao gồm bộ nhớ, máy in, CPU, tệp mở, ổ đĩa băng, CD-ROM, v.v.

Bế tắc : Bế tắc là tình huống hoặc điều kiện khi hai hoặc nhiều quy trình đang nắm giữ một số tài nguyên và cố gắng có được một số tài nguyên khác và chúng không thể giải phóng tài nguyên cho đến khi hoàn thành việc thực thi.

Tình trạng bế tắc hoặc tình huống

nhập mô tả hình ảnh ở đây

Trong sơ đồ trên có hai quá trình P1p2 và có hai tài nguyên R1R2 .

Tài nguyên R1 được phân bổ cho quy trình P1 và tài nguyên R2 được phân bổ cho quy trình p2 . Để hoàn thành quá trình thực hiện quy trình P1 cần tài nguyên R2 , do đó, yêu cầu P1 cho R2 , nhưng R2 đã được phân bổ cho P2 .

Theo cách tương tự, Quy trình P2 để hoàn thành việc thực hiện cần R1 , nhưng R1 đã được phân bổ cho P1 .

cả hai quá trình không thể giải phóng tài nguyên của chúng cho đến khi và trừ khi chúng hoàn thành việc thực thi. Vì vậy, cả hai đang chờ đợi một tài nguyên khác và họ sẽ chờ đợi mãi mãi. Vì vậy, đây là một điều kiện DEADLOCK .

Để bế tắc xảy ra, bốn điều kiện phải đúng.

  1. Loại trừ lẫn nhau - Mỗi tài nguyên hiện được phân bổ cho chính xác một quy trình hoặc có sẵn. (Hai quy trình không thể đồng thời kiểm soát cùng một tài nguyên hoặc nằm trong phần quan trọng của chúng).
  2. Giữ và chờ - các quy trình hiện đang giữ tài nguyên có thể yêu cầu tài nguyên mới.
  3. Không có quyền ưu tiên - Một khi một quy trình giữ một tài nguyên, nó không thể bị lấy đi bởi một quy trình khác hoặc kernel.
  4. Chờ thông tư - Mỗi quy trình đang chờ để có được một tài nguyên được tổ chức bởi một quy trình khác.

và tất cả các điều kiện này được thỏa mãn trong sơ đồ trên.


8

Một bế tắc xảy ra khi một chủ đề đang chờ đợi một cái gì đó không bao giờ xảy ra.

Thông thường, nó xảy ra khi một chủ đề đang chờ trên một mutex hoặc semaphore không bao giờ được phát hành bởi chủ sở hữu trước đó.

Nó cũng thường xảy ra khi bạn gặp tình huống liên quan đến hai luồng và hai khóa như thế này:

Thread 1               Thread 2

Lock1->Lock();         Lock2->Lock();
WaitForLock2();        WaitForLock1();   <-- Oops!

Bạn thường phát hiện ra chúng bởi vì những điều bạn mong đợi sẽ không bao giờ xảy ra hoặc ứng dụng bị treo hoàn toàn.


Một bế tắc xảy ra khi một chủ đề đang chờ đợi một cái gì đó không thể xảy ra.
Hầu tước Lorne

4

Bạn có thể xem qua các bài viết tuyệt vời này , trong phần Deadlock . Đó là trong C # nhưng ý tưởng vẫn giống với nền tảng khác. Tôi trích dẫn ở đây để dễ đọc

Một sự bế tắc xảy ra khi hai luồng mỗi luồng chờ một tài nguyên được giữ bởi tài nguyên kia, vì vậy không thể tiến hành. Cách dễ nhất để minh họa điều này là với hai khóa:

object locker1 = new object();
object locker2 = new object();

new Thread (() => {
                    lock (locker1)
                    {
                      Thread.Sleep (1000);
                      lock (locker2);      // Deadlock
                    }
                  }).Start();
lock (locker2)
{
  Thread.Sleep (1000);
  lock (locker1);                          // Deadlock
}

4

Bế tắc là một vấn đề phổ biến trong các vấn đề đa xử lý / đa chương trình trong HĐH. Giả sử có hai quy trình P1, P2 và hai tài nguyên có thể chia sẻ toàn cầu R1, R2 và trong phần quan trọng cả hai tài nguyên cần được truy cập

Ban đầu, HĐH chỉ định R1 xử lý P1 và R2 để xử lý P2. Vì cả hai quá trình đang chạy đồng thời, chúng có thể bắt đầu thực thi mã của mình nhưng VẤN ĐỀ phát sinh khi một quy trình chạm vào phần quan trọng. Vì vậy, quá trình R1 sẽ đợi quá trình P2 phát hành R2 và ngược lại ... Vì vậy, họ sẽ chờ mãi mãi (ĐIỀU KIỆN DEADLOCK).

PHÂN TÍCH nhỏ ...

Mẹ của bạn (HĐH),
Bạn (P1),
Anh trai của bạn (P2),
Apple (R1),
Dao (R2),
phần quan trọng (cắt táo bằng dao).

Mẹ của bạn đưa cho bạn quả táo và con dao cho anh trai của bạn ngay từ đầu.
Cả hai đều vui vẻ và chơi (Thực hiện mã của họ).
Bất cứ ai trong số bạn muốn cắt táo (phần quan trọng) tại một số điểm.
Bạn không muốn đưa quả táo cho anh trai của bạn.
Anh trai của bạn không muốn đưa con dao cho bạn.
Vì vậy, cả hai bạn sẽ chờ đợi trong một thời gian rất dài :)


2

Bế tắc xảy ra khi hai luồng khóa khóa ngăn chặn một trong hai tiến trình. Cách tốt nhất để tránh chúng là phát triển cẩn thận. Nhiều hệ thống nhúng bảo vệ chống lại chúng bằng cách sử dụng bộ đếm thời gian theo dõi (bộ hẹn giờ đặt lại hệ thống bất cứ khi nào nếu nó bị treo trong một khoảng thời gian nhất định).


2

Bế tắc xảy ra khi có một chuỗi các luồng hoặc quy trình mà mỗi chuỗi giữ một tài nguyên bị khóa và đang cố gắng khóa một tài nguyên được giữ bởi phần tử tiếp theo trong chuỗi. Ví dụ, hai luồng giữ khóa A và khóa B tương ứng và cả hai đều cố gắng để có được khóa khác.


Tôi bỏ phiếu cho bạn. Câu trả lời của bạn ngắn gọn hơn ở trên vì chúng làm cho sự bế tắc khó hiểu xảy ra theo quy trình hoặc luồng. Một số người nói quá trình, một số người nói chủ đề :)
hainguyen

1

Một chương trình cổ điển và rất đơn giản để hiểu tình huống Bế tắc : -

public class Lazy {

    private static boolean initialized = false;

    static {
        Thread t = new Thread(new Runnable() {
            public void run() {
                initialized = true;
            }
        });

        t.start();

        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println(initialized);
    }
}

Khi luồng chính gọi Lazy.main, nó sẽ kiểm tra xem lớp Lazy có được khởi tạo hay không và bắt đầu khởi tạo lớp. Bây giờ luồng chính đặt khởi tạo thành false, tạo và bắt đầu một luồng nền có phương thức chạy được khởi tạo thành true và chờ cho luồng nền hoàn thành.

Lần này, lớp hiện đang được khởi tạo bởi một luồng khác. Trong các trường hợp này, luồng hiện tại, là luồng nền, chờ trên đối tượng Class cho đến khi khởi tạo xong. Thật không may, luồng đang thực hiện khởi tạo, luồng chính, đang chờ luồng nền hoàn thành. Bởi vì hai luồng đang chờ nhau, chương trình bị CHẾT.


0

Bế tắc là trạng thái của một hệ thống trong đó không có quy trình / luồng đơn lẻ nào có khả năng thực hiện một hành động. Như đã đề cập bởi những người khác, bế tắc thường là kết quả của tình huống trong đó mỗi quy trình / luồng muốn có được một khóa đối với tài nguyên đã bị khóa bởi một tiến trình / luồng khác (hoặc thậm chí giống nhau).

Có nhiều phương pháp khác nhau để tìm ra chúng và tránh chúng. Một là suy nghĩ rất khó khăn và / hoặc cố gắng nhiều thứ. Tuy nhiên, đối phó với song song là rất khó khăn và hầu hết (nếu không phải tất cả) mọi người sẽ không thể tránh hoàn toàn các vấn đề.

Một số phương pháp chính thức hơn có thể hữu ích nếu bạn nghiêm túc trong việc xử lý các loại vấn đề này. Phương pháp thiết thực nhất mà tôi biết là sử dụng phương pháp lý thuyết quá trình. Tại đây, bạn mô hình hóa hệ thống của mình bằng một số ngôn ngữ quy trình (ví dụ: CCS, CSP, ACP, mCRL2, LOTOS) và sử dụng các công cụ có sẵn để (model-) kiểm tra các khóa chết (và có lẽ một số thuộc tính khác). Ví dụ về bộ công cụ để sử dụng là FDR, mCRL2, CADP và Uppaal. Một số linh hồn dũng cảm thậm chí có thể chứng minh hệ thống của họ bế tắc miễn phí bằng cách sử dụng các phương pháp tượng trưng thuần túy (chứng minh định lý; tìm kiếm Owicki-Gries).

Tuy nhiên, các phương pháp chính thức này thường đòi hỏi một số nỗ lực (ví dụ: học những điều cơ bản của lý thuyết quá trình). Nhưng tôi đoán đó đơn giản là hậu quả của thực tế là những vấn đề này rất khó.


0

Bế tắc là một tình huống xảy ra khi có ít số lượng tài nguyên có sẵn do nó được yêu cầu bởi quy trình khác nhau. Điều đó có nghĩa là khi số lượng tài nguyên khả dụng trở nên ít hơn số lượng người dùng yêu cầu thì tại thời điểm đó, quá trình chờ đợi. Một số lần chờ đợi tăng lên nhiều hơn và không có bất kỳ cơ hội nào để kiểm tra vấn đề thiếu tài nguyên sau đó tình trạng này được gọi là bế tắc. Trên thực tế, bế tắc là một vấn đề lớn đối với chúng tôi và nó chỉ xảy ra trong hệ điều hành đa nhiệm. Khóa không thể xảy ra trong hệ điều hành tác vụ đơn vì tất cả các tài nguyên chỉ hiện diện cho tác vụ đó hiện đang chạy ......


0

Trên một số giải thích là tốt đẹp. Hy vọng điều này cũng có thể hữu ích: https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html

Trong cơ sở dữ liệu, khi một phiên (ví dụ ora) muốn một tài nguyên được giữ bởi một phiên khác (ví dụ: dữ liệu), nhưng phiên đó (dữ liệu) cũng muốn một tài nguyên được giữ bởi phiên đầu tiên (ora). Có thể có hơn 2 phiên tham gia nhưng ý tưởng sẽ giống nhau. Trên thực tế, Deadlocks ngăn chặn một số giao dịch tiếp tục hoạt động. Ví dụ: Giả sử, ORA-DATA giữ khóa A và yêu cầu khóa B Và SKU giữ khóa B và yêu cầu khóa A.

Cảm ơn,


0

Bế tắc xảy ra khi một luồng đang chờ luồng khác kết thúc và ngược lại.

Làm sao để tránh?
- Tránh các khóa lồng nhau
- Tránh các khóa không cần thiết
- Sử dụng nối chuỗi ()

Làm thế nào để bạn phát hiện ra nó?
chạy lệnh này trong cmd:

jcmd $PID Thread.print

tài liệu tham khảo : geekforgeek


0

Bế tắc không chỉ xảy ra với ổ khóa, mặc dù đó là nguyên nhân thường xuyên nhất. Trong C ++, bạn có thể tạo bế tắc với hai luồng và không có khóa bằng cách chỉ có mỗi lệnh gọi hàm jo () trên đối tượng std :: thread cho đối tượng kia.


0

Kiểm soát tương tranh dựa trên khóa

Việc sử dụng khóa để kiểm soát quyền truy cập vào các tài nguyên được chia sẻ dễ bị bế tắc và một mình bộ lập lịch giao dịch không thể ngăn chặn sự xuất hiện của chúng.

Ví dụ, các hệ thống cơ sở dữ liệu quan hệ sử dụng các khóa khác nhau để đảm bảo các thuộc tính ACID giao dịch .

Cho dù bạn đang sử dụng hệ thống cơ sở dữ liệu quan hệ nào, các khóa sẽ luôn được lấy khi sửa đổi (ví dụ: UPDATEhoặc DELETE) một bản ghi bảng nhất định. Nếu không khóa một hàng đã được sửa đổi bởi một giao dịch hiện đang chạy, Atomicity sẽ bị xâm phạm .

Bế tắc là gì

Như tôi đã giải thích trong bài viết này , một sự bế tắc xảy ra khi hai giao dịch đồng thời không thể đạt được tiến bộ bởi vì mỗi giao dịch chờ người kia phát hành khóa, như được minh họa trong sơ đồ sau.

nhập mô tả hình ảnh ở đây

Bởi vì cả hai giao dịch đang trong giai đoạn mua lại khóa, không ai phát hành khóa trước khi có được giao dịch tiếp theo.

Phục hồi từ một tình huống bế tắc

Nếu bạn đang sử dụng thuật toán Kiểm soát tương tranh dựa trên các khóa, thì luôn có nguy cơ chạy trong tình huống bế tắc. Bế tắc có thể xảy ra trong bất kỳ môi trường đồng thời, không chỉ trong một hệ thống cơ sở dữ liệu.

Chẳng hạn, một chương trình đa luồng có thể bế tắc nếu hai hoặc nhiều luồng đang chờ trên các khóa đã được mua trước đó để không có luồng nào có thể thực hiện bất kỳ tiến trình nào. Nếu điều này xảy ra trong một ứng dụng Java, JVM không thể buộc Thread phải dừng thực thi và giải phóng các khóa của nó.

Ngay cả khi Threadlớp phơi bày một stopphương thức, phương thức đó đã bị từ chối kể từ Java 1.1 vì nó có thể khiến các đối tượng bị bỏ lại ở trạng thái không nhất quán sau khi một luồng bị dừng. Thay vào đó, Java định nghĩa một interruptphương thức, hoạt động như một gợi ý như một luồng bị gián đoạn có thể chỉ cần bỏ qua sự gián đoạn và tiếp tục thực hiện nó.

Vì lý do này, một ứng dụng Java không thể phục hồi sau tình huống bế tắc và nhà phát triển ứng dụng có trách nhiệm phải ra lệnh cho các yêu cầu mua lại khóa theo cách mà các khóa chết không bao giờ có thể xảy ra.

Tuy nhiên, một hệ thống cơ sở dữ liệu không thể thực thi một lệnh mua lại khóa nhất định vì không thể thấy trước những khóa nào khác mà một giao dịch nhất định sẽ muốn có thêm. Giữ nguyên trật tự khóa trở thành trách nhiệm của lớp truy cập dữ liệu và cơ sở dữ liệu chỉ có thể hỗ trợ khôi phục từ tình huống bế tắc.

Công cụ cơ sở dữ liệu chạy một quy trình riêng biệt để quét biểu đồ xung đột hiện tại cho các chu kỳ chờ khóa (nguyên nhân là do các khóa chết). Khi một chu kỳ được phát hiện, công cụ cơ sở dữ liệu chọn một giao dịch và hủy bỏ nó, khiến các khóa của nó được giải phóng, để giao dịch kia có thể đạt được tiến bộ.

Không giống như JVM, một giao dịch cơ sở dữ liệu được thiết kế như một đơn vị công việc nguyên tử. Do đó, một rollback rời khỏi cơ sở dữ liệu ở trạng thái nhất quán.

Để biết thêm chi tiết về chủ đề này, hãy xem bài viết này là tốt.


-2

Mutex về bản chất là một khóa, cung cấp quyền truy cập được bảo vệ vào các tài nguyên được chia sẻ. Trong Linux, kiểu dữ liệu mutex luồng là pthread_mutex_t. Trước khi sử dụng, khởi tạo nó.

Để truy cập vào các tài nguyên được chia sẻ, bạn phải khóa trên mutex. Nếu mutex đã có trên khóa, cuộc gọi sẽ chặn chuỗi cho đến khi mutex được mở khóa. Sau khi hoàn thành chuyến thăm các tài nguyên được chia sẻ, bạn phải mở khóa chúng.

Nhìn chung, có một vài nguyên tắc cơ bản bất thành văn:

  • Có được khóa trước khi sử dụng các tài nguyên được chia sẻ.

  • Giữ khóa càng ngắn càng tốt.

  • Nhả khóa nếu luồng trả về lỗi.


3
Điều này mô tả một khóa, không phải là một bế tắc.
Hầu tước Lorne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.