2009-12-12 33 views

Trả lời

15

Quy tắc đơn giản là luôn luôn có được khóa của bạn theo thứ tự có thể dự đoán nhất quán từ mọi nơi trong ứng dụng của bạn. Ví dụ: nếu tài nguyên của bạn có tên, hãy luôn khóa chúng theo thứ tự bảng chữ cái. Nếu họ có id số, luôn luôn khóa từ thấp nhất đến cao nhất. Thứ tự hoặc tiêu chí chính xác là tùy ý. Điều quan trọng là phải nhất quán. Bằng cách đó bạn sẽ không bao giờ có một tình trạng bế tắc. ví dụ.

  1. Chủ đề 1 ổ khóa tài nguyên Một
  2. Chủ đề 2 ổ khóa tài nguyên B
  3. Chủ đề 1 chờ đợi để có được một khóa trên B
  4. Chủ đề 2 chờ đợi để có được một khóa trên Một
  5. Deadlock

Điều trên không bao giờ có thể xảy ra nếu bạn tuân theo quy tắc chung được nêu ở trên. Để có một cuộc thảo luận chi tiết hơn, hãy xem Wikipedia entry on the Dining Philosophers problem.

0

đọc Deadlock: the Problem and a Solution.

"Lời khuyên chung để tránh bế tắc là phải luôn luôn khóa hai mutexes theo thứ tự: nếu bạn luôn khóa mutex A trước khi mutex B, sau đó bạn sẽ không bao giờ bế tắc. Đôi khi điều này là đơn giản, vì các mutexes đang phục vụ các mục đích khác nhau, nhưng lần khác nó không đơn giản như vậy, chẳng hạn như khi các mutexes từng bảo vệ một cá thể riêng biệt của cùng một lớp ".

2

Cố gắng tránh mua một khóa và cố gắng mua một khóa khác. Điều này có thể dẫn đến sự phụ thuộc vòng tròn và gây ra bế tắc. Nếu không thể tránh được thì ít nhất là lệnh có thể dự đoán được.

Sử dụng RAII (để đảm bảo khóa là phát hành đúng trong trường hợp ngoại lệ cũng)

+1

Điều đó không có vẻ thực tế. Có rất nhiều tình huống mà bạn cần phải giữ nhiều hơn một khóa tại một thời điểm. Điều này đặc biệt đúng nếu bạn sử dụng khóa dạng hạt thay vì một khóa toàn cục. –

+1

Một cách để thực hiện điều này thực tế hơn là sử dụng một API cho phép bạn có được một tập hợp các khóa cùng một lúc. Nếu bạn biết bạn cần khóa A, B và C, khóa cuộc gọi (A, B, C) và chức năng sẽ cố gắng khóa tất cả chúng và thực hiện theo đúng thứ tự. Bạn có thể tự viết một hàm như vậy, để tập trung mã để lấy khóa theo đúng thứ tự. – jalf

+0

tại sao không thực tế? Nó là an toàn hơn để phát hành cuộc gọi mutex trước khi nhập vào một thành phần khác (có bằng cách cố gắng để có được một khóa). "Giữ và chờ" là một trong những lý do cơ bản cho bế tắc –

7
  1. Nếu có thể, thiết kế mã của bạn để bạn không bao giờ phải khóa hơn sau đó một mutex đơn/semaphore tại một thời gian.
  2. Nếu không thể, hãy đảm bảo luôn khóa nhiều mutex/semaphores theo cùng một thứ tự. Vì vậy, nếu một phần của mã khóa mutex A và sau đó có semaphore B, hãy chắc chắn rằng không có phần nào khác của mã có semaphore B và sau đó khóa mutex A.
+0

+1 Chính xác câu trả lời tôi sắp đưa ra. Lời khuyên lập trình đồng thời tốt nhất mà tôi từng nghe. – paxos1977

1

Không có phương pháp chữa trị bế tắc đơn giản.

Nhận khóa theo thứ tự đã thỏa thuận: Nếu tất cả các cuộc gọi có được A-> B-> C thì không có bế tắc nào có thể xảy ra. Deadlocks có thể xảy ra chỉ khi thứ tự khóa khác nhau giữa hai chủ đề (một mua lại A-> B thứ hai B-> A).

Trong thực tế, khó chọn được thứ tự giữa các đối tượng tùy ý trong bộ nhớ. Trên một dự án tầm thường đơn giản là có thể, nhưng trên các dự án lớn với nhiều người đóng góp cá nhân là rất khó. Một giải pháp một phần là tạo phân cấp, bằng cách xếp hạng khóa. Tất cả các khóa trong mô-đun A có cấp bậc 1, tất cả các khóa trong mô-đun B đều có bậc 2. Người ta có thể có được một khóa cấp bậc 2 khi khóa ổ khóa hạng 1, nhưng không phải ngược lại. Tất nhiên bạn cần một khuôn khổ xung quanh các nguyên thủy khóa theo dõi và xác nhận thứ hạng.

+0

Trong C++, việc chọn một đơn đặt hàng rất đơn giản. Chỉ cần đặt hàng theo địa chỉ bộ nhớ. Trong C# hoặc Java, nó phức tạp hơn vì địa chỉ của một đối tượng không được tiếp xúc với lập trình viên và có thể thay đổi trong suốt vòng đời của đối tượng. – jalf

+0

Tôi muốn mọi thứ sẽ dễ dàng như vậy ... Nếu bạn đang ở vị trí hiếm hoi mà mã có danh sách các đối tượng để khóa a, b, c có, bạn có thể so sánh các địa chỉ. Nhưng hầu hết thời gian một bị khóa trong một hàm f(), b bị khóa trong g() và c bị khóa din h() và không có hàm nào trong số này biết về hàm kia. Đó là loại không thực tế để giả định trong một ứng dụng mà tất cả các khóa sẽ được mua lại trong các khối, biết tất cả các khóa sẽ là gì và tham chiếu đến mỗi khóa. –

0

Một cách để đảm bảo thứ tự mà những người khác đã nói đến là mua khóa theo thứ tự được xác định bởi địa chỉ bộ nhớ của chúng. Nếu tại bất kỳ thời điểm nào, bạn cố gắng có được một khóa cần phải có trước đó trong trình tự, bạn giải phóng tất cả các ổ khóa và bắt đầu lại.

Với một công việc nhỏ, có thể thực hiện việc này gần như tự động với một số lớp bao bọc xung quanh các nguyên thủy của hệ thống.

0

Không có thực hành chữa bệnh. Cụ thể, không có cách nào để kiểm tra mã để được đồng bộ hóa chính xác hoặc để các lập trình viên tuân thủ các quy tắc của quý ông với màu xanh lá cây V.

Không có cách nào để kiểm tra mã đa luồng đúng cách, vì logic chương trình có thể phụ thuộc về thời gian của việc mua lại khóa, và do đó, khác với thực thi để thực hiện, bằng cách nào đó làm mất hiệu lực khái niệm về QA.

tôi sẽ nói

  • thích sử dụng đề chỉ như là một tối ưu hóa hiệu suất cho máy đa lõi
  • chỉ tối ưu hóa hiệu suất khi bạn chắc chắn cần thực hiện này
  • bạn có thể sử dụng chủ đề để đơn giản hóa chương trình logic, nhưng chỉ khi bạn hoàn toàn chắc chắn những gì bạn đang làm. Hãy cẩn thận hơn và tất cả các khóa được giới hạn trong một đoạn mã rất nhỏ. Đừng để bất kỳ người mới nào gần mã như vậy.
  • bao giờ sử dụng chủ đề trong một hệ thống nhiệm vụ quan trọng, chẳng hạn như bay một máy bay hoặc điều khiển máy móc nguy hiểm
  • trong mọi trường hợp, chủ đề là hiếm khi chi phí-hiệu quả, do debug cao hơn và bảo đảm chất lượng giá

Nếu bạn quyết tâm làm bài hoặc duy trì codebase hiện có:

  • nhốt tất cả các ổ khóa để miếng nhỏ và mã đơn giản, hoạt động trên nguyên thủy
  • tránh các cuộc gọi chức năng hoặc nhận được lưu lượng chương trình đi đến nơi mà thực tế được thực hiện dưới khóa không phải là ngay lập tức nhìn thấy được. Chức năng này sẽ thay đổi bởi các tác giả tương lai, mở rộng khoảng khóa của bạn mà không có sự kiểm soát của bạn.
  • lấy khóa bên trong các đối tượng để giảm phạm vi khóa, bọc các đối tượng bên thứ ba không an toàn với các giao diện an toàn chỉ của riêng bạn.
  • không bao giờ gửi thông báo đồng bộ (callbacks) khi thực hiện dưới khóa
  • chỉ sử dụng ổ khóa RAII, để giảm tải nhận thức khi nghĩ "làm thế nào khác chúng ta có thể thoát khỏi đây", như trong trường hợp ngoại lệ, vv

Một vài từ về cách tránh đa luồng.

Thiết kế đơn luồng thường liên quan đến một số chức năng nhịp tim được cung cấp bởi các thành phần chương trình và được gọi là vòng lặp (gọi là chu kỳ nhịp tim), khi được gọi, tạo cơ hội cho tất cả các thành phần thực hiện công việc tiếp theo và đầu hàng kiểm soát trở lại. Những gì các nhà toán học muốn nghĩ đến như là "vòng" bên trong các thành phần, sẽ biến thành máy nhà nước, để xác định điều tiếp theo cần được thực hiện khi được gọi là gì. Nhà nước được duy trì tốt nhất là dữ liệu thành viên của các đối tượng tương ứng.

+0

Cố gắng đa luồng trên một ứng dụng * sau khi * nó đã được viết là một công thức cho sự thất bại. Nếu bạn định sử dụng các chủ đề, hãy làm đúng, và nghĩ nó vào kiến ​​trúc của ứng dụng từ ngày 1. Nếu không, bạn * sẽ * nhận được tất cả các deadlocks khó chịu và các điều kiện chủng tộc. Và chúng ta hãy thành thật, trừ khi bạn vẫn còn sống trong những năm 80, bất kỳ ứng dụng nào giả vờ thậm chí là một chút chuyên sâu về CPU (hoặc chỉ cần phải đáp ứng) thực sự không thể tránh được luồng. – jalf

0

Có rất nhiều "phương pháp chữa trị bế tắc" đơn giản. Nhưng không có ứng dụng nào dễ sử dụng và hoạt động phổ biến.

Điều đơn giản nhất, tất nhiên là "không bao giờ có nhiều hơn một chuỗi".

Giả sử bạn có một ứng dụng đa luồng tuy nhiên, vẫn còn một số giải pháp:

Bạn có thể cố gắng hạn chế tối đa tình trạng chia sẻ và đồng bộ hóa. Hai luồng chỉ chạy song song và không bao giờ tương tác có thể không bao giờ bế tắc. Deadlocks chỉ xảy ra khi nhiều chủ đề cố gắng truy cập cùng một tài nguyên. Tại sao họ làm điều đó? Điều đó có thể tránh được không? Tài nguyên có thể được cấu trúc lại hay phân chia sao cho ví dụ, một luồng có thể ghi vào nó, và các luồng khác được truyền thông tin không đồng bộ một cách không đồng bộ?

Có lẽ tài nguyên có thể được sao chép, cho mỗi luồng bản sao riêng tư của riêng nó hoạt động cùng?

Và như đã được đề cập bởi mỗi câu trả lời khác, nếu và khi bạn cố gắng để có được ổ khóa, làm như vậy theo một trật tự nhất quán toàn cầu. Để đơn giản hóa điều này, bạn nên cố gắng đảm bảo rằng tất cả các khóa mà một chuỗi sẽ cần được mua như một thao tác đơn lẻ. Nếu một chủ đề cần lấy khóa A, B và C, thì không được thực hiện ba cuộc gọi lock() vào các thời điểm khác nhau và từ các địa điểm khác nhau. Bạn sẽ bị lẫn lộn, và bạn sẽ không thể theo dõi những khóa nào được giữ bởi sợi chỉ, và cái nào mà nó chưa có, và sau đó bạn sẽ làm rối trật tự. Nếu bạn có thể có được tất cả các khóa bạn cần một lần, sau đó bạn có thể đưa nó vào một cuộc gọi chức năng riêng biệt mà mua lại N khóa, và làm như vậy theo thứ tự đúng để tránh deadlocks.

Sau đó, có nhiều cách tiếp cận tham vọng hơn: Các kỹ thuật như CSP làm cho luồng cực kỳ đơn giản và dễ dàng để chứng minh chính xác, ngay cả với hàng nghìn chủ đề đồng thời. Nhưng nó đòi hỏi bạn phải cấu trúc chương trình của bạn rất khác với những gì bạn đang sử dụng.

Transactional Memory là một tùy chọn đầy hứa hẹn khác và có thể dễ dàng tích hợp vào các chương trình thông thường hơn. Nhưng việc triển khai chất lượng sản xuất vẫn còn rất hiếm.

0

Nếu bạn muốn tấn công khả năng bế tắc, bạn phải tấn công một trong 4 điều kiện quan trọng cho sự tồn tại của bế tắc.

4 điều kiện cho bế tắc là: 1. Loại trừ lẫn nhau - chỉ một chuỗi có thể nhập phần quan trọng tại một thời điểm. 2. Giữ và Chờ - một chuỗi không giải phóng tài nguyên mà anh ta có được miễn là anh ấy không hoàn thành công việc của mình ngay cả khi các tài nguyên khác không có sẵn. 3. Không được ưu tiên - Một chuỗi không có ưu tiên so với các chủ đề khác. 4. Chu kỳ tài nguyên - Phải có một chuỗi chu trình của các chuỗi chờ các tài nguyên từ các luồng khác.

Điều kiện dễ nhất để tấn công là chu kỳ tài nguyên bằng cách đảm bảo rằng không có chu kỳ nào có thể.

Các vấn đề liên quan