2010-03-10 36 views
45

Tôi hiểu mutex đệ quy cho phép khóa nhiều lần nhiều lần mà không bị bế tắc và phải được mở khóa cùng một số lần. Nhưng trong những tình huống cụ thể nào bạn cần phải sử dụng một mutex đệ quy? Tôi đang tìm các tình huống thiết kế/mã.Khi nào nên sử dụng mutex đệ quy?

+5

Không thực sự là một bản dupe, nhưng chồng chéo: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex. Câu hỏi đó hỏi "tại sao trên trái đất có ai muốn sử dụng một mutex không đệ quy". Câu hỏi này hỏi "tại sao mọi người lại muốn sử dụng mutex đệ quy?" Nó giống như các nền văn minh ngoài hành tinh va chạm ;-) –

+0

@Steve: Vâng, tôi đã kiểm tra chuỗi đó trước đây nhưng tôi không nhận được câu trả lời mà tôi đang tìm kiếm ... Tôi thực sự đang tìm kiếm các thiết kế cụ thể nơi nó thực sự cần thiết. – jasonline

+1

Tôi khá chắc chắn không có, hoặc ít nhất là không có thiết kế đơn giản và rõ ràng-tốt, đủ để phục vụ như các ứng dụng sát thủ. Bất kỳ chức năng nào có một mutex đều có thể được thay thế bằng hai hàm, một hàm nhận nó và một hàm không có. Bất kỳ chức năng nào muốn gọi một hàm như vậy có thể gọi hàm thích hợp theo liệu mutex được đề cập đã được giữ chưa. Bất kỳ thiết kế nào mà bạn gọi chức năng mà không biết liệu bạn có giữ một mutex có liên quan đến chức năng đó hay không, có thể là hỏng. Nhưng hãy xem hầu như bất kỳ Java nào để xem mã mà có được ngắn gọn từ khóa đệ quy. –

Trả lời

38

Ví dụ khi bạn có chức năng mà các cuộc gọi nó một cách đệ quy, và bạn muốn để có được đồng bộ truy cập vào nó:

void foo() { 
    ... mutex_acquire(); 
    ... foo(); 
    ... mutex_release(); 
} 

mà không có một mutex đệ quy bạn sẽ phải tạo ra một "điểm vào" chức năng đầu tiên, và điều này trở nên rườm rà khi bạn có một tập hợp các hàm trùng lặp. Nếu không có mutex đệ quy:

void foo_entry() { 
    mutex_acquire(); foo(); mutex_release(); } 

void foo() { ... foo(); ... } 
+4

Mặc dù điều đó đúng, khóa có rất nhiều chi phí, và do đó, có thể không phải là một ý tưởng tồi khi tạo các phiên bản không an toàn của chuỗi, trước tiên, sau đó tạo một trình bao bọc threadsafe nhẹ cho nó. –

+3

@Michael: nếu việc triển khai mutex của bạn hỗ trợ khóa đệ quy, thì rất có thể nó sẽ hỗ trợ nó một cách hiệu quả. Tất cả những gì thường được yêu cầu là chức năng khóa không "if (mutex.owner == thisthread) {++ lockcount; return;}". Nếu bạn không có khóa thì bạn đang đọc trường chủ sở hữu không đồng bộ, nhưng miễn là việc triển khai biết rằng việc đọc và ghi của trường mutex.owner là nguyên tử (ví dụ: đọc từ trên x86), bạn không thể nhận được thông báo lỗi tích cực. Nếu bạn có khóa thì bạn không thể có được một âm tính giả bởi vì không có gì có thể thay đổi chủ sở hữu trong khi bạn giữ nó. –

+0

"bạn không thể nhận được xác thực sai".Tôi có lẽ nên đề cập rằng có một số giả định ở đó - chủ yếu là mỗi khi một chuỗi thu thập hoặc giải phóng một mutex, hạt nhân đảm bảo rằng trường mutex.owner được thay đổi theo cách hiển thị cho chuỗi đó (hoặc thay đổi nó từ hoặc xóa bộ nhớ cache của chủ đề của nó hoặc bất kỳ thứ gì). –

1

Nó chắc chắn sẽ là một vấn đề nếu một sợi bị chặn cố gắng để có được (một lần nữa) một mutex nó đã thuộc sở hữu ...

Có một lý do để không cho phép một mutex được được mua nhiều lần bởi cùng một chuỗi?

+1

Tính đơn giản của việc triển khai, tôi nghĩ vậy. Ngoài ra, đây là một yếu tố hợp lý chống lại các mutex đệ quy: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex/293646#293646. Tôi nghi ngờ Java có thể đã xóa bỏ khá nhiều sự quen thuộc của lập trình viên trung bình với, hoặc khả năng sử dụng, các khóa không đệ quy. –

+0

C++ được thiết kế để trở thành một ngôn ngữ rất nhanh. Không giống như một số ngôn ngữ phổ biến khác, C++ cố gắng cung cấp các triển khai thực hiện các hoạt động của xương trần (chẳng hạn như std :: mutex) làm công việc cơ bản của họ nhanh nhất có thể. Nếu bạn cần sử dụng đối tượng nổi bật hơn (như std :: recursive_mutex) với chi phí nhỏ trên đầu, nó cũng có sẵn. Vấn đề là cung cấp cho nhà phát triển khả năng viết mã nhanh khi họ cần. –

19

Các đoạn ngắt đệ quy và không đệ quy có các trường hợp sử dụng khác nhau. Không có loại mutex nào có thể dễ dàng thay thế loại khác. Các mutex không đệ quy có ít chi phí hơn, và các mutex đệ quy có trong một số trường hợp ngữ nghĩa hữu ích hoặc thậm chí cần thiết và trong các tình huống khác là ngữ nghĩa nguy hiểm hoặc thậm chí bị phá vỡ. Trong hầu hết các trường hợp, ai đó có thể thay thế bất kỳ chiến lược nào bằng cách sử dụng các mutex đệ quy với một chiến lược an toàn hơn và hiệu quả hơn dựa trên việc sử dụng các mutex không đệ quy.

  • Nếu bạn chỉ muốn loại trừ chủ đề khác từ việc sử dụng mutex bảo vệ tài nguyên của bạn, sau đó bạn có thể sử dụng bất kỳ mutex loại, nhưng có thể muốn sử dụng mutex không đệ quy vì chi phí nhỏ hơn của nó.
  • Nếu bạn muốn gọi chức năng đệ quy, trong đó khóa mutex cùng, sau đó họ hoặc
    • phải sử dụng một đệ quy mutex, hoặc
    • đã để mở khóa và khóa cùng một mutex không đệ quy một lần nữa và một lần nữa (hãy xem xét các chủ đề đồng thời!) (giả sử đây là âm thanh ngữ nghĩa, nó vẫn có thể là vấn đề về hiệu suất), hoặc
    • phải bằng cách nào đó chú thích những mutex nào đã bị khóa (mô phỏng quyền sở hữu/mutex đệ quy).
  • Nếu bạn muốn khóa một số đối tượng mutex được bảo vệ từ một tập các đối tượng như vậy, nơi các bộ có thể đã được xây dựng bằng cách sáp nhập, bạn có thể chọn
    • để sử dụng cho mỗi đối tượng chính xác một mutex , cho phép chủ đề hơn để làm việc song song, hoặc
    • để sử dụng cho mỗi đối tượng một tài liệu tham khảo cho bất kỳ có thể chia sẻ mutex đệ quy, để giảm xác suất thất bại trong việc khóa tất cả mutex es với nhau, hoặc
    • để sử dụng cho mỗi đối tượng một tài liệu tham khảo có thể so sánh với bất kỳcó thể chia sẻ mutex không đệ quy, phá vỡ mục đích để khóa nhiều lần.
  • Nếu bạn muốn nhả khóa trong một chủ đề khác so với khóa đã bị khóa, bạn phải sử dụng khóa không đệ quy (hoặc khóa đệ quy cho phép điều này thay vì ném ngoại lệ).
  • Nếu bạn muốn sử dụng biến đồng bộ, thì bạn cần phải là có thể mở khóa rõ ràng mutex trong khi chờ trên bất kỳ biến đồng bộ nào để tài nguyên được phép sử dụng trong các chủ đề khác. Điều đó chỉ có thể thực hiện được với các đột biến không đệ quy, vì các mutex đệ quy có thể đã bị khóa bởi người gọi hàm hiện tại.
+4

Không chắc chắn tôi đồng ý rằng "phải mở khóa và khóa cùng một mutex không đệ quy một lần nữa và một lần nữa (hãy cẩn thận các chủ đề đồng thời!)" Là khả thi, ngay cả với cảnh báo. Nếu bạn chưa sẵn sàng phát hành tài nguyên (hoặc nó ở trạng thái không nhất quán hoặc bạn không muốn bất kỳ ai khác mucking về nó), hãy _not_ mở khóa/recur/lock. Tôi đảm bảo tại một số điểm thread khác sẽ lẻn vào và cắn bạn nơi mặt trời không tỏa sáng :-) Tôi sẽ không downvote vì phần còn lại của câu trả lời là thực sự rất tốt - Tôi chỉ nghĩ rằng tôi muốn mang lại cho lên. – paxdiablo

+1

Xem http://stackoverflow.com/questions/10546867/why-would-we-want-to-make-function-recursive-which-has-a-mutex-lock cho những gì đã mua hàng này. – paxdiablo

+1

và tại đây: http://stackoverflow.com/q/10548284/462608 –

3

Nếu bạn muốn xem ví dụ về mã sử dụng mutexes đệ quy, hãy xem các nguồn cho "Hàng rào điện" cho Linux/Unix. 'Twas một trong những công cụ Unix phổ biến cho việc tìm kiếm "giới hạn kiểm tra" đọc/ghi overruns và underruns cũng như sử dụng bộ nhớ đã được giải phóng, trước khi Valgrind đến cùng.

Chỉ cần biên dịch và liên kết hàng rào điện với nguồn (tùy chọn -g với gcc/g ++), sau đó liên kết nó với phần mềm của bạn bằng tùy chọn liên kết -lefence và bắt đầu bước qua các cuộc gọi đến malloc/miễn phí. http://elinux.org/Electric_Fence

2

Tôi gặp phải nhu cầu về mutex đệ quy ngày hôm nay, và tôi nghĩ đây có thể là ví dụ đơn giản nhất trong số các câu trả lời đã đăng cho đến thời điểm này: Đây là lớp hiển thị hai hàm API, Quy trình (...) và đặt lại().

public void Process(...) 
{ 
    acquire_mutex(mMutex); 
    // Heavy processing 
    ... 
    reset(); 
    ... 
    release_mutex(mMutex); 
} 

public void reset() 
{ 
    acquire_mutex(mMutex); 
    // Reset 
    ... 
    release_mutex(mMutex); 
} 

Cả hai chức năng không được chạy đồng thời vì chúng sửa đổi nội bộ của lớp, vì vậy tôi muốn sử dụng một mutex. Vấn đề là, Process() gọi reset() nội bộ, và nó sẽ tạo ra một bế tắc vì mMutex đã được mua lại. Khóa chúng bằng khóa đệ quy thay vì khắc phục sự cố.

+3

Chỉ cần tạo một phiên bản riêng của 'reset()' sẽ không khóa mutex để sử dụng nội bộ. API công cộng 'reset()' để khóa mutex và gọi thiết lập lại nội bộ và mở khóa mutex. Đây là lý do vô lý để giới thiệu các mutex đệ quy. Để tối đa hóa tính song song, bạn nên giữ khóa như một lượng thời gian nhỏ nhất có thể, các mutex đệ quy không giúp được điều này - ngược lại. – FooF

+1

Mất bao lâu để một mutex phụ thuộc nhiều vào nhiệm vụ trong tầm tay.Có lẽ bạn chỉ mã GUI; những người khác mã các mặt hàng chế biến nặng cần phải được tắt tiếng. –

+0

mutexes là kẻ giết người đồng thời, đó là toàn bộ quan điểm của họ. Ngay sau khi invariances của bạn đang ở trong hình dạng tốt, bạn mở khóa để các chủ đề xử lý nặng khác có thể làm công việc của họ. Xem tuyệt vời David Butenhof nói về các mutex đệ quy: http://www.zaval.org/resources/library/butenhof1.html (chúng chỉ nên được sử dụng như một "cái nạng" cho đến khi bạn nhận được mã an toàn không phải là chủ đề được tái cấu trúc trong thread-safe nhà nước với ổ khóa cụ thể, ông cũng nghĩ rằng trong trường hợp này, bạn chỉ cần một khóa toàn cầu duy nhất). – FooF

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