Mit POSIX können Mutexe rekursiv sein. Das bedeutet, dass derselbe Thread denselben Mutex zweimal sperren kann und nicht blockiert. Natürlich muss es auch zweimal entsperrt werden, sonst kann kein anderer Thread den Mutex erhalten. Nicht alle Systeme, die Pthreads unterstützen, unterstützen auch rekursive Mutexe. Wenn sie jedoch POSIX-konform sein möchten, müssen sie dies tun .
Andere APIs (höherwertige APIs) bieten normalerweise auch Mutexe an, die häufig als Sperren bezeichnet werden. Einige Systeme / Sprachen (z. B. Cocoa Objective-C) bieten sowohl rekursive als auch nicht rekursive Mutexe. Einige Sprachen bieten auch nur die eine oder andere an. Beispielsweise sind Mutexe in Java immer rekursiv (derselbe Thread kann zweimal für dasselbe Objekt "synchronisiert" werden). Abhängig davon, welche anderen Thread-Funktionen sie anbieten, ist es möglicherweise kein Problem, keine rekursiven Mutexe zu haben, da sie leicht selbst geschrieben werden können (ich habe rekursive Mutexe bereits selbst auf der Grundlage einfacherer Mutex- / Bedingungsoperationen implementiert).
Was ich nicht wirklich verstehe: Wofür sind nicht rekursive Mutexe gut? Warum sollte ich einen Thread-Deadlock haben wollen, wenn er denselben Mutex zweimal sperrt? Selbst Hochsprachen, die dies vermeiden könnten (z. B. Testen, ob dies zum Stillstand kommt, und Auslösen einer Ausnahme, wenn dies der Fall ist), tun dies normalerweise nicht. Sie lassen stattdessen den Thread blockieren.
Ist dies nur in Fällen der Fall, in denen ich es versehentlich zweimal und nur einmal entsperre und im Falle eines rekursiven Mutex ist es schwieriger, das Problem zu finden. Stattdessen muss es sofort blockiert werden, um festzustellen, wo die falsche Sperre angezeigt wird. Aber könnte ich nicht dasselbe tun, wenn beim Entsperren ein Sperrzähler zurückgegeben wird und in einer Situation, in der ich sicher bin, dass ich die letzte Sperre freigegeben habe und der Zähler nicht Null ist, kann ich eine Ausnahme auslösen oder das Problem protokollieren? Oder gibt es einen anderen, nützlicheren Anwendungsfall für nicht rekursive Mutexe, den ich nicht sehe? Oder ist es vielleicht nur Leistung, da ein nicht rekursiver Mutex etwas schneller sein kann als ein rekursiver? Ich habe dies jedoch getestet und der Unterschied ist wirklich nicht so groß.
Die Antwort ist nicht Effizienz. Nicht wiedereintretende Mutexe führen zu besserem Code.
Beispiel: A :: foo () erwirbt die Sperre. Es ruft dann B :: bar () auf. Das hat gut funktioniert, als du es geschrieben hast. Aber irgendwann später ändert jemand B :: bar (), um A :: baz () aufzurufen, das auch die Sperre erwirbt.
Wenn Sie keine rekursiven Mutexe haben, blockiert dies. Wenn Sie sie haben, läuft es, aber es kann brechen. A :: foo () hat das Objekt möglicherweise vor dem Aufruf von bar () in einem inkonsistenten Zustand belassen, unter der Annahme, dass baz () nicht ausgeführt werden konnte, da es auch den Mutex abruft. Aber es sollte wahrscheinlich nicht laufen! Die Person, die A :: foo () geschrieben hat, ging davon aus, dass niemand gleichzeitig A :: baz () aufrufen kann - das ist der ganze Grund, warum beide Methoden die Sperre erworben haben.
Das richtige mentale Modell für die Verwendung von Mutexen: Der Mutex schützt eine Invariante. Wenn der Mutex gehalten wird, kann sich die Invariante ändern, aber bevor der Mutex freigegeben wird, wird die Invariante wiederhergestellt. Wiedereintretende Sperren sind gefährlich, da Sie beim zweiten Erwerb der Sperre nicht mehr sicher sein können, ob die Invariante wahr ist.
Wenn Sie mit wiedereintretenden Sperren zufrieden sind, liegt dies nur daran, dass Sie ein solches Problem noch nicht debuggen mussten. Java hat heutzutage übrigens nicht wiedereintretende Sperren in java.util.concurrent.locks.
quelle
Semaphore
.A::foo()
Möglicherweise befindet sich das Objekt vor dem Aufruf noch in einem inkonsistenten ZustandA::bar()
. Was hat rekursiver oder nicht rekursiver Mutex mit diesem Fall zu tun?Wie von Dave Butenhof selbst geschrieben :
"Das größte aller großen Probleme mit rekursiven Mutexen ist, dass sie Sie dazu ermutigen, Ihr Sperrschema und Ihren Umfang vollständig aus den Augen zu verlieren. Dies ist tödlich. Böse. Es ist der" Fadenfresser ". Sie halten Sperren für die absolut kürzestmögliche Zeit. Punkt. Immer. Wenn Sie etwas mit gehaltenem Schloss anrufen, nur weil Sie nicht wissen, dass es gehalten wird, oder weil Sie nicht wissen, ob der Angerufene den Mutex benötigt, halten Sie ihn zu lange Richten Sie eine Schrotflinte auf Ihre Anwendung und drücken Sie den Abzug. Vermutlich haben Sie damit begonnen, Threads zu verwenden, um Parallelität zu erhalten. Sie haben jedoch gerade Parallelität verhindert. "
quelle
...you're not DONE until they're [recursive mutex] all gone.. Or sit back and let someone else do the design.
Warum sind Sie sicher, dass dies wirklich das richtige mentale Modell für die Verwendung von Mutexen ist? Ich denke, das richtige Modell schützt Daten, aber keine Invarianten.
Das Problem des Schutzes von Invarianten tritt auch in Single-Thread-Anwendungen auf und hat mit Multithreading und Mutexen nichts gemeinsam.
Wenn Sie Invarianten schützen müssen, können Sie dennoch ein binäres Semaphor verwenden, das niemals rekursiv ist.
quelle
Ein Hauptgrund dafür, dass rekursive Mutexe nützlich sind, ist der mehrfache Zugriff auf die Methoden durch denselben Thread. Angenommen, die Mutex-Sperre schützt eine Bank-Klimaanlage vor Abhebungen. Wenn mit dieser Abhebung auch eine Gebühr verbunden ist, muss derselbe Mutex verwendet werden.
quelle
Der einzig gute Anwendungsfall für Rekursionsmutex ist, wenn ein Objekt mehrere Methoden enthält. Wenn eine der Methoden den Inhalt des Objekts ändert und daher das Objekt sperren muss, bevor der Status wieder konsistent ist.
Wenn die Methoden andere Methoden verwenden (dh: addNewArray () ruft addNewPoint () auf und wird mit recheckBounds () abgeschlossen), aber jede dieser Funktionen muss den Mutex für sich sperren, dann ist rekursiver Mutex eine Win-Win-Situation.
In jedem anderen Fall (nur schlechte Codierung lösen, sogar in verschiedenen Objekten verwenden) ist dies eindeutig falsch!
quelle
Sie sind absolut gut, wenn Sie sicherstellen müssen, dass der Mutex entsperrt ist, bevor Sie etwas tun. Dies liegt daran
pthread_mutex_unlock
, dass garantiert werden kann, dass der Mutex nur dann entsperrt wird, wenn er nicht rekursiv ist.Wenn dies
g_mutex
nicht rekursiv ist, wird der obige Code garantiertbar()
mit entsperrtem Mutex aufgerufen .Das Ausschalten der Möglichkeit eines Deadlocks für den Fall, dass
bar()
es sich um eine unbekannte externe Funktion handelt, kann durchaus dazu führen, dass ein anderer Thread versucht, denselben Mutex zu erhalten. Solche Szenarien sind in Anwendungen, die auf Thread-Pools basieren, und in verteilten Anwendungen, in denen ein Interprozessaufruf möglicherweise einen neuen Thread erzeugt, nicht ungewöhnlich, ohne dass der Client-Programmierer dies überhaupt bemerkt. In all diesen Szenarien ist es am besten, die genannten externen Funktionen erst aufzurufen, nachdem die Sperre aufgehoben wurde.Wenn
g_mutex
es rekursiv wäre, gäbe es einfach keine Möglichkeit , sicherzustellen, dass es entsperrt ist, bevor Sie einen Anruf tätigen.quelle