Wydaje się, że w C ++ 14 pominięto mechanizm sprawdzania, czy std::mutex
blokada jest zablokowana, czy nie. Zobacz to SO pytanie:
/programming/21892934/how-to-assert-if-a-stdmutex-is-locked
Jest na to kilka sposobów, np. Przy użyciu;
std::mutex::try_lock()
std::unique_lock::owns_lock()
Ale żadne z nich nie jest szczególnie satysfakcjonującym rozwiązaniem.
try_lock()
dozwolone jest zwracanie fałszywego ujemnego i ma niezdefiniowane zachowanie, jeśli bieżący wątek zablokował muteks. Ma również skutki uboczne. owns_lock()
wymaga budowy unique_lock
na górze oryginału std::mutex
.
Oczywiście mógłbym stworzyć własne, ale wolałbym zrozumieć motywy obecnego interfejsu.
Możliwość sprawdzenia statusu muteksu (np. std::mutex::is_locked()
) Nie wydaje mi się ezoteryczną prośbą, więc podejrzewam, że Komitet Standardowy celowo pominął tę funkcję, a nie przeoczenie.
Czemu?
Edycja: Ok, więc może ten przypadek użycia nie jest tak powszechny, jak się spodziewałem, więc zilustruję mój konkretny scenariusz. Mam algorytm uczenia maszynowego, który jest rozłożony na wiele wątków. Każdy wątek działa asynchronicznie i wraca do puli głównej po zakończeniu problemu z optymalizacją.
Następnie blokuje główny muteks. Wątek musi następnie wybrać nowego rodzica, od którego można mutować potomstwo, ale może wybrać tylko od rodziców, którzy obecnie nie mają potomstwa, które jest optymalizowane przez inne wątki. Dlatego muszę przeprowadzić wyszukiwanie, aby znaleźć rodziców, którzy nie są obecnie zablokowani przez inny wątek. Nie ma ryzyka zmiany statusu muteksu podczas wyszukiwania, ponieważ muteks głównego wątku jest zablokowany. Oczywiście istnieją inne rozwiązania (obecnie używam flagi logicznej), ale myślałem, że mutex oferuje logiczne rozwiązanie tego problemu, ponieważ istnieje on w celu synchronizacji między wątkami.
is_locked
?Odpowiedzi:
Widzę co najmniej dwa poważne problemy z sugerowaną operacją.
Pierwszy został już wspomniany w komentarzu @ gnasher729 :
Jedynym sposobem, aby upewnić się, że właściwość mutexu „jest obecnie zablokowana”, nie zmienia się, cóż, zablokować ją samodzielnie.
Drugi problem, jaki widzę, polega na tym, że dopóki nie zablokujesz muteksu, twój wątek nie zsynchronizuje się z wątkiem, który wcześniej zablokował muteks. Dlatego nie jest nawet dobrze zdefiniowane mówienie o „przed” i „po”, a to, czy muteks jest zamknięty, czy nie, jest rodzajem pytania, czy kot Schrödigera żyje bez próby otwarcia pudełka.
Jeśli dobrze rozumiem, oba problemy byłyby dyskusyjne w twoim konkretnym przypadku dzięki zablokowaniu głównego muteksu. Ale nie wydaje mi się to szczególnie częstym przypadkiem, więc uważam, że komitet postąpił słusznie, nie dodając funkcji, która może być nieco przydatna w bardzo szczególnych scenariuszach i powodować szkody we wszystkich innych. (W duchu: „Spraw, aby interfejsy były łatwe w użyciu i trudne w użyciu”).
I jeśli mogę powiedzieć, myślę, że konfiguracja, którą obecnie posiadasz, nie jest najbardziej elegancka i może zostać zrefaktoryzowana, aby całkowicie uniknąć problemu. Na przykład zamiast głównego wątku sprawdzającego wszystkich potencjalnych rodziców pod kątem takiego, który nie jest obecnie zablokowany, dlaczego nie utrzymywać kolejki gotowych rodziców? Jeśli wątek chce zoptymalizować inny, wyrzuca następny z kolejki i gdy tylko ma nowych rodziców, dodaje je do kolejki. W ten sposób nie potrzebujesz nawet głównego wątku jako koordynatora.
źródło
Wygląda na to, że używasz wtórnych muteksów nie do blokowania dostępu do problemu optymalizacji, ale do ustalenia, czy problem optymalizacji jest obecnie optymalizowany czy nie.
To jest całkowicie niepotrzebne. Miałbym listę problemów, które wymagają optymalizacji, listę problemów, które są obecnie optymalizowane, oraz listę problemów, które zostały zoptymalizowane. (Nie bierz „listy” dosłownie, rozumiem przez to „odpowiednią strukturę danych).
Operacje dodawania nowego problemu do listy niezoptymalizowanych problemów lub przenoszenia problemu z jednej listy do następnej byłyby wykonywane pod ochroną pojedynczego muteksu „master”.
źródło
std::mutex
jest odpowiedni dla takiej struktury danych?std::mutex
opiera się na implementacji mutex zdefiniowanej przez system operacyjny, która może dobrze zajmować zasoby (np. uchwyty), które są ograniczone i wolno przydzielają i / lub działają. Użycie pojedynczego muteksu do zablokowania dostępu do wewnętrznej struktury danych może być znacznie bardziej wydajne i być może również bardziej skalowalne.Jak powiedzieli inni, nie ma przypadku użycia, w którym
is_locked
muteks przynosi jakiekolwiek korzyści, dlatego funkcja nie istnieje.Sprawa masz problem jest bardzo powszechne, to w zasadzie co zrobić wątków roboczych, które są jednym z, jeśli nie z najczęstszych realizację wątkach.
Masz półkę z 10 pudełkami. Z tymi skrzynkami pracuje 4 pracowników. Jak upewnić się, że 4 pracowników pracuje na różnych skrzyniach? Pierwszy pracownik zdejmuje pudełko z półki, zanim zacznie nad nim pracować. Drugi pracownik widzi 9 pudełek na półce.
Nie ma muteksów do blokowania pudełek, więc widzenie stanu fikcyjnego muteksu na pudełku nie jest konieczne, a nadużywanie muteksu jako wartości logicznej jest po prostu błędne. Mutex blokuje półkę.
źródło
Oprócz dwóch powodów podanych w odpowiedzi 5gon12eder powyżej, chciałbym dodać, że nie jest to ani konieczne, ani pożądane.
Jeśli już trzymasz muteks, to lepiej wiedzieć, że go trzymasz! Nie musisz pytać. Podobnie jak w przypadku posiadania bloku pamięci lub innego zasobu, powinieneś dokładnie wiedzieć, czy jesteś jego właścicielem, a kiedy należy zwolnić / usunąć zasób.
Jeśli tak nie jest, twój program jest źle zaprojektowany i masz kłopoty.
Jeśli chcesz uzyskać dostęp do współdzielonego zasobu chronionego przez muteks, a mutex nie jest już w posiadaniu, musisz go zdobyć. Nie ma innej opcji, w przeciwnym razie logika programu jest nieprawidłowa.
Może się okazać, że blokowanie jest akceptowalne lub niedopuszczalne, w obu przypadkach
lock()
lubtry_lock()
da pożądane zachowanie. Wszystko, co musisz wiedzieć, pozytywnie i bez wątpienia, to czy udało ci się zdobyć muteks (zwracana wartośćtry_lock
mówi). Nie ma znaczenia, czy ktoś ją trzyma, czy też masz fałszywą porażkę.W każdym innym przypadku, wprost, to nie twoja sprawa. Nie musisz wiedzieć, nie powinieneś wiedzieć ani przyjmować założeń (dotyczących terminowości i problemów z synchronizacją wymienionych w drugim pytaniu).
źródło
try_lock
pierwszy zasób, a jeśli ten się nie powiedzie, wypróbuj drugi. Przykład: Trzy trwałe, połączone w pulę połączenia z serwerem bazy danych i musisz użyć jednego, aby wysłać polecenie.is_locked()
może ułatwić takie zachowanie.is_locked
, istnieje znacznie lepsze rozwiązanie twojego problemu niż to, o którym myślisz.Być może chcesz użyć atomic_flag z domyślną kolejnością pamięci. Nie ma wyścigów danych i nigdy nie zgłasza wyjątków, takich jak mutex z wieloma wywołaniami odblokowania (i przerywa niekontrolowane przerwanie, mógłbym dodać ...). Alternatywnie istnieje atomowy (np. Atomowy [bool] lub atomowy [int] (z nawiasami trójkątnymi, a nie [])), który ma ładne funkcje, takie jak load i Compare_exchange_strong.
źródło
Chcę dodać do tego przypadek użycia: Pozwoliłoby to funkcji wewnętrznej, aby zapewnić jako warunek wstępny / stwierdzenie, że dzwoniący rzeczywiście trzyma blokadę.
W przypadku klas z kilkoma takimi funkcjami wewnętrznymi i prawdopodobnie wieloma funkcjami publicznymi, które je wywołują, może zapewnić, że ktoś dodający inną funkcję publiczną wywołującą funkcję wewnętrzną rzeczywiście uzyskał blokadę.
źródło