Tại sao tôi không thể kiểm tra xem một mutex có bị khóa không?


28

C ++ 14 dường như đã bỏ qua một cơ chế để kiểm tra xem có std::mutexbị khóa hay không. Xem câu hỏi SO này:

https://stackoverflow.com/questions/21892934/how-to-assert-if-a-stdmutex-is-locked

Có một số cách xung quanh điều này, ví dụ bằng cách sử dụng;

std::mutex::try_lock()
std::unique_lock::owns_lock()

Nhưng cả hai đều không phải là giải pháp đặc biệt thỏa mãn.

try_lock()được phép trả lại âm tính giả và có hành vi không xác định nếu luồng hiện tại đã khóa mutex. Nó cũng có tác dụng phụ. owns_lock()yêu cầu xây dựng unique_locktrên đầu trang của bản gốc std::mutex.

Rõ ràng là tôi có thể tự lăn, nhưng tôi muốn hiểu các động lực cho giao diện hiện tại.

Khả năng kiểm tra trạng thái của một mutex (ví dụ std::mutex::is_locked()) dường như không phải là một yêu cầu bí truyền đối với tôi, vì vậy tôi nghi ngờ Ủy ban Tiêu chuẩn cố tình bỏ qua tính năng này thay vì là một sự giám sát.

Tại sao?

Chỉnh sửa: Ok vì vậy có thể trường hợp sử dụng này không phổ biến như tôi mong đợi, vì vậy tôi sẽ minh họa kịch bản cụ thể của mình. Tôi có một thuật toán học máy được phân phối trên nhiều luồng. Mỗi luồng hoạt động không đồng bộ và trở về nhóm chính sau khi hoàn thành vấn đề tối ưu hóa.

Sau đó nó khóa một mutex master. Sau đó, chủ đề phải chọn một bố mẹ mới để đột biến con cái, nhưng chỉ có thể chọn từ bố mẹ hiện không có con cái đang được tối ưu hóa bởi các chủ đề khác. Do đó, tôi cần phải thực hiện tìm kiếm để tìm cha mẹ hiện không bị khóa bởi một luồng khác. Không có rủi ro về tình trạng của mutex thay đổi trong quá trình tìm kiếm, vì mutex chủ đề bị khóa. Rõ ràng là có các giải pháp khác (hiện tôi đang sử dụng cờ boolean) nhưng tôi nghĩ rằng mutex cung cấp một giải pháp hợp lý cho vấn đề này, vì nó tồn tại cho mục đích đồng bộ hóa giữa các luồng.


42
Bạn thực sự không thể kiểm tra một cách hợp lý liệu một mutex có bị khóa hay không, bởi vì một nano giây sau khi kiểm tra nó có thể được mở khóa hoặc khóa hay không. Vì vậy, nếu bạn đã viết "if (mutex_is_locked ()) ..." thì mutex_is_locked có thể trả về kết quả chính xác, nhưng tại thời điểm "if" được thực thi, nó đã sai.
gnasher729

1
Cái này ^ Những thông tin hữu ích nào bạn hy vọng nhận được từ is_locked?
Vô dụng

3
Điều này cảm thấy như một vấn đề XY. Tại sao bạn chỉ cố gắng ngăn chặn việc tái sử dụng của cha mẹ trong khi một đứa trẻ đang được tạo ra? Bạn có một yêu cầu rằng bất kỳ cha mẹ có thể chỉ có chính xác một đứa con? Khóa của bạn sẽ không ngăn chặn điều đó. Bạn không có thế hệ rõ ràng? Nếu không, bạn có biết rằng những cá nhân có thể được tối ưu hóa nhanh hơn có thể lực cao hơn, vì họ có thể được chọn thường xuyên hơn / sớm hơn? Nếu bạn sử dụng các thế hệ, tại sao bạn không chọn tất cả các bậc cha mẹ ở phía trước, sau đó để các chủ đề lấy cha mẹ từ một hàng đợi? Là tạo ra con cái thực sự tốn kém đến mức bạn cần nhiều chủ đề?
amon

10
@quant - Tôi không thấy lý do tại sao đối tượng cha mẹ của bạn biến đổi trong ứng dụng ví dụ của bạn cần phải là mutexes: nếu bạn có một mutex chính bị khóa bất cứ khi nào chúng được đặt, bạn chỉ có thể sử dụng biến boolean để biểu thị trạng thái của chúng.
Periata Breatta

4
Tôi không đồng ý với câu cuối cùng của câu hỏi. Một giá trị boolean đơn giản sạch sẽ hơn nhiều so với một mutex ở đây. Biến nó thành một bool nguyên tử nếu bạn không muốn khóa mutex chính để "trả lại" cha mẹ.
Sebastian Redl

Câu trả lời:


53

Tôi có thể thấy ít nhất hai vấn đề nghiêm trọng với hoạt động được đề xuất.

Cái đầu tiên đã được đề cập trong một bình luận của @ gnasher729 :

Bạn thực sự không thể kiểm tra một cách hợp lý liệu một mutex có bị khóa hay không, bởi vì một nano giây sau khi kiểm tra nó có thể được mở khóa hoặc khóa hay không. Vì vậy, nếu bạn đã viết if (mutex_is_locked ()) …thì mutex_is_lockedcó thể trả về kết quả chính xác, nhưng vào thời điểm ifthực thi, nó đã sai.

Cách duy nhất để chắc chắn rằng hiện tại, hiện đang bị khóa tài sản của một mutex không thay đổi là, hãy tự khóa nó.

Vấn đề thứ hai tôi thấy là trừ khi bạn khóa mutex, luồng của bạn không đồng bộ hóa với luồng đã khóa mutex trước đó. Do đó, thậm chí còn không được định nghĩa rõ ràng để nói về những điều trước đây và trước khi nói về vấn đề này và liệu mutex có bị khóa hay không là cách hỏi liệu con mèo của Schrödiger có còn sống mà không cố mở hộp không.

Nếu tôi hiểu chính xác, thì cả hai vấn đề sẽ được khắc phục trong trường hợp cụ thể của bạn nhờ vào mutex chính bị khóa. Nhưng đây không phải là trường hợp đặc biệt phổ biến đối với tôi nên tôi nghĩ rằng ủy ban đã làm đúng bằng cách không thêm một chức năng có thể hữu ích trong các tình huống rất đặc biệt và gây thiệt hại cho tất cả những người khác. (Theo tinh thần của: Làm cho giao diện dễ sử dụng một cách chính xác và khó sử dụng không chính xác.

Và nếu tôi có thể nói, tôi nghĩ rằng thiết lập mà bạn hiện có không phải là thanh lịch nhất và có thể được cấu trúc lại để tránh vấn đề hoàn toàn. Ví dụ, thay vì luồng chính kiểm tra tất cả các bậc cha mẹ tiềm năng cho một cha mẹ hiện không bị khóa, tại sao không duy trì hàng đợi cha mẹ sẵn sàng? Nếu một chủ đề muốn tối ưu hóa một chủ đề khác, nó sẽ bật cái tiếp theo ra khỏi hàng đợi và ngay khi nó có cha mẹ mới, nó sẽ thêm chúng vào hàng đợi. Theo cách đó, bạn thậm chí không cần chủ đề chính như một điều phối viên.


Cảm ơn, đây là một câu trả lời tốt. Lý do tôi không muốn duy trì một hàng dài các bậc cha mẹ sẵn sàng là vì tôi cần phải giữ trật tự mà cha mẹ đã tạo ra (vì điều này quyết định tuổi thọ của họ). Điều này được thực hiện dễ dàng với hàng đợi LIFO. Nếu tôi bắt đầu kéo mọi thứ ra vào, tôi sẽ phải duy trì một cơ chế đặt hàng riêng biệt sẽ làm phức tạp mọi thứ, do đó là cách tiếp cận hiện tại.
lượng

14
@quant: Nếu bạn có hai mục đích để xếp hàng cha mẹ, bạn có thể thực hiện điều đó với hai hàng đợi ....

@quant: Bạn đang xóa một mục (nhiều nhất) một lần, nhưng có lẽ đang xử lý trên mỗi lần nhiều lần, vì vậy bạn đang tối ưu hóa trường hợp hiếm gặp với chi phí của trường hợp phổ biến. Điều này hiếm khi được mong muốn.
Jerry Coffin

2
Nhưng nó hợp lý để hỏi liệu chủ đề hiện tại đã khóa mutex.
Chuộc tội giới hạn

@LrictAtonement Không thực sự. Để làm điều này, mutex phải lưu trữ thông tin bổ sung (id luồng), làm cho nó chậm hơn. Mutex đệ quy làm điều này đã có, thay vào đó bạn nên họ.
StaceyGirl

9

Có vẻ như bạn đang sử dụng các mutexes thứ cấp không để khóa quyền truy cập vào một vấn đề tối ưu hóa, nhưng để xác định xem một vấn đề tối ưu hóa có được tối ưu hóa ngay bây giờ hay không.

Điều đó là hoàn toàn không cần thiết. Tôi có một danh sách các vấn đề cần tối ưu hóa, một danh sách các vấn đề đang được tối ưu hóa ngay bây giờ và một danh sách các vấn đề đã được tối ưu hóa. (Đừng lấy "danh sách" theo nghĩa đen, hãy hiểu nó là "bất kỳ cấu trúc dữ liệu phù hợp nào).

Các hoạt động thêm một vấn đề mới vào danh sách các vấn đề chưa được tối ưu hóa, hoặc chuyển một vấn đề từ danh sách này sang danh sách tiếp theo, sẽ được thực hiện dưới sự bảo vệ của mutex "master" duy nhất.


1
Bạn không nghĩ rằng một đối tượng kiểu std::mutexlà phù hợp với cấu trúc dữ liệu như vậy?
lượng

2
@quant - không. std::mutexphụ thuộc vào việc triển khai mutex do hệ điều hành xác định có thể sử dụng tốt các tài nguyên (ví dụ: các thẻ điều khiển) bị hạn chế và chậm phân bổ và / hoặc vận hành. Sử dụng một mutex duy nhất để khóa truy cập vào cấu trúc dữ liệu nội bộ có thể sẽ hiệu quả hơn nhiều và cũng có thể mở rộng hơn.
Periata Breatta

1
Cũng xem xét các biến điều kiện. Họ có thể tạo ra rất nhiều cấu trúc dữ liệu như thế này thực sự dễ dàng.
Cort Ammon - Phục hồi Monica

2

Như những người khác đã nói, không có trường hợp sử dụng nào trong trường hợp đột biến có is_lockedlợi ích gì, đó là lý do tại sao chức năng này không tồn tại.

Trường hợp bạn gặp vấn đề rất phổ biến, về cơ bản, đó là những gì các luồng công nhân làm, một trong những, nếu không phải triển khai phổ biến nhất của các luồng.

Bạn có một kệ với 10 hộp trên đó. Bạn có 4 công nhân làm việc với các hộp này. Làm thế nào để bạn chắc chắn rằng 4 công nhân làm việc trên các hộp khác nhau? Công nhân đầu tiên lấy một cái hộp ra khỏi kệ trước khi họ bắt đầu làm việc với nó. Công nhân thứ hai nhìn thấy 9 hộp trên kệ.

Không có mutex để khóa các hộp, vì vậy việc nhìn thấy trạng thái của mutex tưởng tượng trên hộp là không cần thiết, và lạm dụng một mutex như một boolean là sai. Các mutex khóa kệ.


1

Ngoài hai lý do được đưa ra trong câu trả lời của 5gon12eder ở trên, tôi muốn nói thêm rằng nó không cần thiết cũng không mong muốn.

Nếu bạn đang giữ một mutex, thì bạn nên biết rằng bạn đang giữ nó! Bạn không cần phải hỏi. Giống như việc sở hữu một khối bộ nhớ hoặc bất kỳ tài nguyên nào khác, bạn nên biết chính xác mình có sở hữu nó hay không và khi nào thì thích hợp để giải phóng / xóa tài nguyên.
Nếu đó không phải là trường hợp, chương trình của bạn được thiết kế tồi và bạn đang gặp rắc rối.

Nếu bạn cần truy cập vào tài nguyên được chia sẻ được bảo vệ bởi mutex và bạn chưa nắm giữ mutex, thì bạn cần phải có được mutex. Không có lựa chọn nào khác, nếu không thì logic chương trình của bạn không đúng.
Bạn có thể thấy việc chặn có thể chấp nhận hoặc không thể chấp nhận được, trong cả hai trường hợp lock()hoặc try_lock()sẽ đưa ra hành vi bạn muốn. Tất cả những gì bạn cần biết, một cách tích cực, và không nghi ngờ gì, là liệu bạn có mua thành công mutex hay không (giá trị trả về try_lockcho bạn biết). Không quan trọng cho dù người khác giữ nó hay bạn có thất bại giả hay không.

Trong mọi trường hợp khác, nói thẳng ra, đó không phải là việc của bạn. Bạn không cần phải biết và bạn không nên biết hoặc đưa ra các giả định (về các vấn đề đồng thời và đồng bộ được đề cập trong câu hỏi khác).


1
Điều gì xảy ra nếu tôi muốn thực hiện thao tác xếp hạng trên các tài nguyên hiện có để khóa?
lượng

Nhưng đó có phải là một cái gì đó thực tế để xảy ra? Tôi cho rằng điều đó khá bất thường. Tôi muốn nói rằng một trong hai tài nguyên đã có một số loại xếp hạng nội tại, sau đó bạn sẽ cần phải thực hiện (có được khóa cho) thứ quan trọng hơn trước. Ví dụ: Phải cập nhật mô phỏng vật lý trước khi kết xuất. Hoặc, xếp hạng ít nhiều có chủ ý, sau đó bạn cũng có thể là try_locktài nguyên đầu tiên, và nếu tài nguyên đó thất bại, hãy thử thứ hai. Ví dụ: Ba kết nối liên tục, được gộp chung với máy chủ cơ sở dữ liệu và bạn cần sử dụng một kết nối để gửi lệnh.
Damon

4
@quant - "một hoạt động xếp hạng trên các tài nguyên hiện đang có sẵn để khóa" - nói chung, thực hiện loại việc này là một cách thực sự dễ dàng và nhanh chóng để viết mã bế tắc theo cách bạn đấu tranh để tìm ra. Làm cho việc mua lại khóa và phát hành xác định là, trong hầu hết các trường hợp, chính sách tốt nhất. Tìm kiếm một khóa dựa trên một tiêu chí có thể thay đổi là yêu cầu rắc rối.
Periata Breatta

@PeriataBreatta Chương trình của tôi cố tình không xác định. Bây giờ tôi thấy thuộc tính này không phổ biến, vì vậy tôi có thể hiểu được thiếu sót của các tính năng như thế is_locked()có thể tạo điều kiện cho hành vi đó.
lượng

@quant xếp hạng và khóa là vấn đề hoàn toàn riêng biệt. Nếu bạn muốn bằng cách nào đó sắp xếp hoặc sắp xếp lại một hàng đợi với một khóa, khóa nó, sắp xếp nó, sau đó mở khóa nó. Nếu bạn cần is_locked, thì một giải pháp tốt hơn cho vấn đề của bạn tồn tại hơn là giải pháp bạn có trong đầu.
Peter

1

Bạn có thể muốn sử dụng nguyên tử_flag với thứ tự bộ nhớ mặc định. Nó không có các cuộc đua dữ liệu và không bao giờ đưa ra các ngoại lệ như mutex thực hiện với nhiều cuộc gọi mở khóa (và hủy bỏ không kiểm soát được, tôi có thể thêm ...). Ngoài ra, có nguyên tử (ví dụ: nguyên tử [bool] hoặc nguyên tử [int] (với dấu ngoặc tam giác, không phải [])), có các chức năng tốt như tải và so sánh_exchange_strong.


1

Tôi muốn thêm trường hợp sử dụng cho việc này: Nó sẽ cho phép một chức năng nội bộ để đảm bảo là điều kiện tiên quyết / khẳng định rằng người gọi thực sự đang giữ khóa.

Đối với các lớp có một số hàm nội bộ như vậy và có thể nhiều hàm công khai gọi chúng, có thể đảm bảo rằng ai đó thêm một hàm công khai khác gọi hàm nội bộ thực sự có được khóa.

class SynchronizedClass
{

public:

void publicFunc()
{
  std::lock_guard<std::mutex>(_mutex);

  internalFuncA();
}

// A lot of code

void newPublicFunc()
{
  internalFuncA(); // whops, forgot to acquire the lock
}


private:

void internalFuncA()
{
  assert(_mutex.is_locked_by_this_thread());

  doStuffWithLockedResource();
}

};
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.