it-swarm.com.de

Rekursive Sperre (Mutex) vs Nicht-Rekursive Sperre (Mutex)

Mit POSIX können Mutexe rekursiv sein. Das bedeutet, dass derselbe Thread denselben Mutex zweimal sperren kann und kein Deadlock auftritt. 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, aber wenn sie POSIX-konform sein sollen, müssen sie .

Andere APIs (High-Level-APIs) bieten normalerweise auch Mutexe an, die oft als Locks bezeichnet werden. Einige Systeme/Sprachen (z. B. Cocoa Objective-C) bieten sowohl rekursive als auch nicht rekursive Mutexe an. Einige Sprachen bieten auch nur die eine oder andere an. Z.B. in Java Mutexe sind immer rekursiv (derselbe Thread kann zweimal auf dasselbe Objekt "synchronisieren".) Je nachdem, welche andere Thread-Funktionalität sie bieten, ist es möglicherweise kein Problem, keine rekursiven Mutexe zu haben kann leicht selbst geschrieben werden (ich habe rekursive Mutexe bereits selbst auf der Basis von einfacheren Mutex-/Bedingungsoperationen implementiert).

Was ich nicht wirklich verstehe: Wozu 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 zu einem Deadlock führt und eine Ausnahme auslösen, wenn dies der Fall ist), tun dies normalerweise nicht. Sie lassen stattdessen das Thread Deadlock.

Gilt dies nur für Fälle, in denen ich es versehentlich zweimal sperre und nur einmal entsperre und im Falle eines rekursiven Mutex das Problem schwerer zu finden ist. Ich habe es also sofort gesperrt, um zu sehen, wo die falsche Sperre angezeigt wird. Aber könnte ich nicht das Gleiche tun, wenn beim Entsperren ein Sperrzähler zurückgegeben wird und ich sicher bin, dass ich die letzte Sperre aufgehoben habe und der Zähler nicht Null ist? Ich kann 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? Allerdings habe ich das getestet und der Unterschied ist wirklich nicht so groß.

172
Mecki

Der Unterschied zwischen einem rekursiven und einem nicht rekursiven Mutex liegt im Besitz. Im Fall eines rekursiven Mutex muss der Kernel den Thread verfolgen, der den Mutex das erste Mal tatsächlich erhalten hat, damit er den Unterschied zwischen der Rekursion und einem anderen Thread erkennen kann, der stattdessen blockieren sollte. Wie eine andere Antwort hervorhob, stellt sich die Frage nach dem zusätzlichen Speicheraufwand sowohl für die Speicherung dieses Kontexts als auch für die Zyklen, die zu seiner Aufrechterhaltung erforderlich sind.

Allerdings spielen auch hier andere Überlegungen eine Rolle.

Da der rekursive Mutex ein Gefühl der Eigentümerschaft besitzt, muss der Thread, der den Mutex erfasst, derselbe Thread sein, der den Mutex freigibt. Im Fall von nicht-rekursiven Mutexen gibt es kein Eigentumsgefühl und jeder Thread kann normalerweise den Mutex freigeben, unabhängig davon, welcher Thread ursprünglich den Mutex verwendet hat. In vielen Fällen ist diese Art von "Mutex" eher eine Semaphor-Aktion, bei der Sie den Mutex nicht unbedingt als Ausschlussgerät, sondern als Synchronisations- oder Signalisierungsgerät zwischen zwei oder mehr Threads verwenden.

Eine weitere Eigenschaft, die mit dem Gefühl der Zugehörigkeit zu einem Mutex einhergeht, ist die Fähigkeit, die vorrangige Vererbung zu unterstützen. Da der Kernel den Thread verfolgen kann, der den Mutex besitzt, und auch die Identität aller Blocker, ist es in einem Prioritäts-Thread-System möglich, die Priorität des Threads, dem der Mutex gegenwärtig gehört, auf die Priorität des Threads mit der höchsten Priorität zu erhöhen das ist derzeit auf dem mutex blockiert. Diese Vererbung verhindert das Problem der Prioritätsumkehr, das in solchen Fällen auftreten kann. (Beachten Sie, dass nicht alle Systeme die Vererbung von Prioritäten für solche Mutexe unterstützen, dies ist jedoch eine weitere Funktion, die über den Besitzbegriff möglich wird.).

Wenn Sie sich auf den klassischen VxWorks RTOS Kernel beziehen, definieren sie drei Mechanismen:

  • mutex - unterstützt die Rekursion und optional die Prioritätsvererbung. Dieser Mechanismus wird häufig zum kohärenten Schutz kritischer Datenbereiche verwendet.
  • binäres Semaphor - keine Rekursion, keine Vererbung, einfacher Ausschluss, Abnehmer und Geber müssen nicht derselbe Thread sein, Broadcast-Release verfügbar. Dieser Mechanismus kann zum Schutz kritischer Abschnitte verwendet werden, ist jedoch auch besonders nützlich für die kohärente Signalisierung oder Synchronisierung zwischen Threads.
  • Zählsemaphor - keine Rekursion oder Vererbung, wirkt als kohärenter Ressourcenzähler von jeder gewünschten Anfangszählung, Threads blockieren nur, wenn die Nettozählung für die Ressource Null ist.

Auch dies ist je nach Plattform etwas unterschiedlich - insbesondere was sie diese Dinge nennen, aber dies sollte repräsentativ für die Konzepte und verschiedenen Mechanismen sein, die im Spiel sind.

143
Tall Jeff

Die Antwort lautet nicht Effizienz. Nicht wiedereintretende Mutexe führen zu besserem Code.

Beispiel: A :: foo () erwirbt die Sperre. Anschließend wird B :: bar () aufgerufen. Dies hat gut funktioniert, als Sie es geschrieben haben. 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 in einem inkonsistenten Zustand belassen, bevor bar () aufgerufen wurde, unter der Annahme, dass baz () nicht ausgeführt werden konnte, weil es auch den Mutex erwirbt. Aber es sollte wahrscheinlich nicht laufen! Die Person, die A :: foo () geschrieben hat, hat angenommen, dass niemand gleichzeitig A :: baz () aufrufen kann - das ist der gesamte Grund, warum beide Methoden die Sperre erhalten 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. Wiedereintrittssperren sind gefährlich, da Sie beim zweiten Erwerb der Sperre nicht mehr sicher sein können, dass die Invariante wahr ist.

Wenn Sie mit wiedereintretenden Sperren zufrieden sind, liegt dies nur daran, dass Sie ein Problem wie dieses noch nicht behoben haben. Java hat in diesen Tagen übrigens nicht wiedereintrittsfähige Sperren in Java.util.concurrent.locks.

118
Jonathan

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 Gültigkeitsbereich vollständig aus den Augen zu verlieren. Dies ist tödlich. Das Böse. Es ist der" Fadenfresser ". Sie halten Sperren für die absolut kürzestmögliche Zeit. Punkt Immer Wenn Sie etwas mit einem gehaltenen Schloss anrufen, nur weil Sie nicht wissen, dass es gehalten wird, oder weil Sie nicht wissen, ob der Angerufene das Mutex benötigt, halten Sie es zu lange Richten Sie eine Schrotflinte auf Ihre Anwendung und betätigen Sie den Auslöser. Vermutlich haben Sie angefangen, Threads zu verwenden, um die Parallelität zu ermitteln, aber Sie haben gerade die Parallelität VERHINDERT. "

87
Chris Cleeland

Das richtige mentale Modell für die Verwendung von Mutexen: Der Mutex schützt eine Invariante.

Warum bist du dir 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 selbst bei Anwendungen mit einem Thread auf und hat nichts mit Multithreading und Mutexen gemeinsam.

Wenn Sie Invarianten schützen müssen, können Sie weiterhin binäre Semaphore verwenden, die niemals rekursiv sind.

14
Corpse

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-A/C vor dem Abheben. Wenn mit diesem Abheben auch eine Gebühr verbunden ist, muss derselbe Mutex verwendet werden.

4
avis

Der einzige 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 (z. B. addNewArray (), addNewPoint () aufrufen und mit recheckBounds () abschließen), aber eine dieser Funktionen den Mutex selbst sperren muss, ist der rekursive Mutex eine Win-Win-Situation.

Für jeden anderen Fall (nur schlechte Codierung lösen, auch in anderen Objekten verwenden) ist das eindeutig falsch!

3
DarkZeros