POSIX pozwala rekursywnym muteksom. Oznacza to, że ten sam wątek może dwa razy zablokować ten sam muteks i nie zablokuje się. Oczywiście musi również odblokować go dwa razy, w przeciwnym razie żaden inny wątek nie może uzyskać muteksu. Nie wszystkie systemy obsługujące pthreads również obsługują rekurencyjne muteksy, ale jeśli chcą być zgodne z POSIX, muszą to zrobić .
Inne interfejsy API (bardziej zaawansowane interfejsy API) również zwykle oferują muteksy, często nazywane Blokadami. Niektóre systemy / języki (np. Cocoa Objective-C) oferują zarówno rekurencyjne, jak i nierekurencyjne muteksy. Niektóre języki oferują tylko jeden lub drugi. Np. W Javie muteksy są zawsze rekurencyjne (ten sam wątek może dwukrotnie „synchronizować” na tym samym obiekcie). W zależności od tego, jakie inne funkcje wątku oferują, brak rekurencyjnych muteksów może nie stanowić problemu, ponieważ można je łatwo napisać samodzielnie (sam już zaimplementowałem rekurencyjne muteksy na podstawie prostszych operacji mutex / warunek).
Czego tak naprawdę nie rozumiem: Do czego służą nierekurencyjne muteksy? Dlaczego miałbym chcieć zablokować wątek, jeśli dwukrotnie blokuje ten sam muteks? Nawet języki wysokiego poziomu, które mogłyby tego uniknąć (np. Sprawdzanie, czy to się zakleszczy i zgłaszanie wyjątku, jeśli tak się dzieje) zwykle tego nie robią. Zamiast tego pozwolą na impas w wątku.
Czy dotyczy to tylko przypadków, w których przypadkowo blokuję go dwukrotnie i odblokowuję tylko raz, a w przypadku rekurencyjnego muteksu trudniej byłoby znaleźć problem, więc zamiast tego mam natychmiastową impas, aby zobaczyć, gdzie pojawia się nieprawidłowa blokada? Ale czy nie mogę zrobić tego samego, gdy licznik zamków jest zwracany podczas odblokowywania iw sytuacji, gdy jestem pewien, że zwolniłem ostatnią blokadę, a licznik nie jest równy zero, mogę zgłosić wyjątek lub zarejestrować problem? Czy jest jakiś inny, bardziej użyteczny przypadek użycia nierekurencyjnych muteksów, których nie widzę? A może to tylko wydajność, ponieważ nierekurencyjny muteks może być nieco szybszy niż rekursywny? Jednak przetestowałem to i różnica nie jest tak duża.
Odpowiedź to nie wydajność. Muteksy niezwiązane z rejestracją prowadzą do lepszego kodu.
Przykład: A :: foo () przejmuje blokadę. Następnie wywołuje B :: bar (). To działało dobrze, kiedy to napisałeś. Ale jakiś czas później ktoś zmienia B :: bar (), aby wywołać A :: baz (), który również uzyskuje blokadę.
Cóż, jeśli nie masz rekurencyjnych muteksów, to impas. Jeśli je masz, działa, ale może się zepsuć. A :: foo () mógł pozostawić obiekt w niespójnym stanie przed wywołaniem bar (), przy założeniu, że baz () nie może zostać uruchomiony, ponieważ również nabywa muteks. Ale to chyba nie powinno działać! Osoba, która napisała A :: foo () założyła, że nikt nie może wywołać A :: baz () w tym samym czasie - to jest cały powód, dla którego obie metody uzyskały blokadę.
Właściwy model mentalny do używania muteksów: Muteks chroni niezmiennika. Gdy muteks jest trzymany, niezmiennik może się zmienić, ale przed zwolnieniem muteksu niezmiennik zostaje ponownie ustanowiony. Blokady ponownego wysyłania są niebezpieczne, ponieważ przy drugim zakupie blokady nie możesz być już pewien, że niezmiennik jest prawdziwy.
Jeśli jesteś zadowolony z blokad ponownego wysyłania, to tylko dlatego, że nie musiałeś wcześniej debugować takiego problemu. Nawiasem mówiąc, Java ma obecnie blokadę bez ponownej rejestracji w java.util.concurrent.locks.
źródło
Semaphore
.A::foo()
przed wywołaniem obiekt mógł pozostawić niespójny stanA::bar()
. Co muteks, rekurencyjny czy nie, ma coś wspólnego z tą sprawą?Jak napisał sam Dave Butenhof :
„Największy ze wszystkich dużych problemów z rekurencyjnymi muteksami polega na tym, że zachęcają cię do całkowitego zejścia z planu blokowania i zakresu. To jest śmiertelne. Zło. To„ zjadacz nici ”. Trzymasz zamki przez możliwie najkrótszy czas. Okres. Zawsze. Jeśli dzwonisz z blokadą trzymaną tylko dlatego, że nie wiesz, czy blokada jest zablokowana, lub ponieważ nie wiesz, czy odbiorca potrzebuje muteksu, to trzymasz go zbyt długo. celowanie strzelbą w aplikację i pociągnięcie za spust. Prawdopodobnie zacząłeś używać wątków, aby uzyskać współbieżność, ale właśnie ZAPOBIEGAŁeś współbieżności. ”
źródło
...you're not DONE until they're [recursive mutex] all gone.. Or sit back and let someone else do the design.
Dlaczego jesteś pewien, że to naprawdę właściwy model mentalny do używania muteksów? Myślę, że odpowiedni model chroni dane, ale nie niezmienniki.
Problem ochrony niezmienników występuje nawet w aplikacjach jednowątkowych i nie ma nic wspólnego z wielowątkowością i muteksami.
Ponadto, jeśli chcesz chronić niezmienniki, nadal możesz użyć binarnego semafora, który nigdy nie jest rekurencyjny.
źródło
Jednym z głównych powodów, dla których rekurencyjne muteksy są przydatne, jest wielokrotne uzyskiwanie dostępu do metod przez ten sam wątek. Powiedzmy na przykład, że jeśli blokada mutex chroni bank A / c przed wypłatą, to jeśli z wypłatą wiąże się również opłata, należy zastosować ten sam mutex.
źródło
Jedynym dobrym przypadkiem użycia muteksu rekurencyjnego jest sytuacja, gdy obiekt zawiera wiele metod. Gdy dowolna z metod modyfikuje zawartość obiektu, a zatem musi zablokować obiekt, zanim stan będzie znów spójny.
Jeśli metody używają innych metod (np .: addNewArray () wywołuje metodę addNewPoint () i kończy się poleceniem recheckBounds ()), ale każda z tych funkcji musi sama zablokować muteks, rekurencyjny muteks jest korzystny dla wszystkich.
W każdym innym przypadku (rozwiązanie po prostu złego kodowania, użycie go nawet w różnych obiektach) jest wyraźnie błędne!
źródło
Są absolutnie dobre, gdy musisz upewnić się, że mutex jest odblokowany przed zrobieniem czegoś. Jest tak, ponieważ
pthread_mutex_unlock
może zagwarantować, że muteks zostanie odblokowany tylko wtedy, gdy nie będzie rekurencyjny.Jeśli
g_mutex
nie jest rekurencyjny, powyższy kod gwarantuje wywołaniebar()
przy odblokowanym muteksie .W ten sposób eliminuje się możliwość zakleszczenia w przypadku
bar()
, gdy zdarza się, że jest to nieznana funkcja zewnętrzna, która może zrobić coś, co może spowodować, że inny wątek będzie próbował uzyskać ten sam muteks. Takie scenariusze nie są rzadkie w aplikacjach zbudowanych na pulach wątków oraz w aplikacjach rozproszonych, w których wywołanie międzyprocesowe może odrodzić nowy wątek, nawet jeśli programista klienta nawet tego nie zauważy. We wszystkich takich scenariuszach najlepiej jest wywoływać wspomniane funkcje zewnętrzne dopiero po zwolnieniu blokady.Gdyby
g_mutex
było rekurencyjne, po prostu nie byłoby sposobu, aby upewnić się, że jest odblokowane przed wykonaniem połączenia.źródło