Warum kann ich nicht überprüfen, ob ein Mutex gesperrt ist?

28

In C ++ 14 scheint ein Mechanismus zur Überprüfung, ob ein std::mutexgesperrt ist, weggelassen worden zu sein . Siehe diese SO-Frage:

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

Es gibt verschiedene Möglichkeiten, um dies zu umgehen, z.

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

Beides ist jedoch keine besonders befriedigende Lösung.

try_lock()darf ein falsches Negativ zurückgeben und hat undefiniertes Verhalten, wenn der aktuelle Thread den Mutex gesperrt hat. Es hat auch Nebenwirkungen. owns_lock()erfordert die Konstruktion eines unique_locküber dem Original std::mutex.

Natürlich könnte ich meine eigenen Rollen spielen, aber ich verstehe lieber die Motivationen für die aktuelle Benutzeroberfläche.

Die Möglichkeit, den Status eines Mutex (z. B. std::mutex::is_locked()) zu überprüfen, scheint mir keine esoterische Aufforderung zu sein, daher vermute ich, dass das Standardkomitee diese Funktion absichtlich ausgelassen hat, anstatt sie zu übersehen.

Warum?

Edit: Ok, vielleicht ist dieser Anwendungsfall nicht so verbreitet, wie ich erwartet hatte, also werde ich mein spezielles Szenario veranschaulichen. Ich habe einen Algorithmus für maschinelles Lernen, der auf mehrere Threads verteilt ist. Jeder Thread arbeitet asynchron und kehrt nach Abschluss eines Optimierungsproblems zu einem Master-Pool zurück.

Dann wird ein Master-Mutex gesperrt. Der Thread muss dann ein neues Elternteil auswählen, von dem ein Nachwuchs mutiert werden soll. Er darf jedoch nur Elternteile auswählen, die derzeit keine Nachwuchskräfte haben, die von anderen Threads optimiert werden. Ich muss daher eine Suche durchführen, um Eltern zu finden, die derzeit nicht von einem anderen Thread gesperrt sind. Es besteht keine Gefahr, dass sich der Status des Mutex während der Suche ändert, da der Master-Thread-Mutex gesperrt ist. Natürlich gibt es andere Lösungen (ich verwende derzeit ein Boolesches Flag), aber ich dachte, der Mutex bietet eine logische Lösung für dieses Problem, da er zum Zwecke der Inter-Thread-Synchronisation existiert.

quant
quelle
42
Sie können nicht wirklich vernünftigerweise überprüfen, ob ein Mutex gesperrt ist, da er eine Nanosekunde nach der Überprüfung entsperrt oder gesperrt werden kann. Wenn Sie also "if (mutex_is_locked ()) ..." geschrieben haben, könnte mutex_is_locked das richtige Ergebnis zurückgeben, aber wenn das "if" ausgeführt wird, ist es falsch.
gnasher729
1
Dies ^. Von welchen nützlichen Informationen erhoffen Sie sich is_locked?
Nutzlos
3
Das fühlt sich wie ein XY-Problem an. Warum versuchen Sie, die Wiederverwendung der Eltern nur zu verhindern, während ein Kind erzeugt wird? Haben Sie die Anforderung, dass ein Elternteil nur genau einen Nachkommen haben darf? Ihr Schloss wird das nicht verhindern. Hast du keine klaren Generationen? Wenn nicht, wissen Sie, dass Personen, die schneller optimiert werden können, eine höhere Fitness haben, da sie häufiger / früher ausgewählt werden können? Wenn Sie Generationen verwenden, warum wählen Sie nicht alle Eltern im Voraus aus und lassen die Threads Eltern aus einer Warteschlange abrufen? Ist das Generieren von Nachkommen wirklich so teuer, dass Sie mehrere Threads benötigen?
amon
10
@quant - Ich verstehe nicht, warum Ihre übergeordneten Objektmutexe in Ihrer Beispielanwendung überhaupt Mutexe sein müssen: Wenn Sie einen Master-Mutex haben, der bei jedem Setzen gesperrt ist, können Sie einfach eine boolesche Variable verwenden, um ihren Status anzuzeigen.
Periata Breatta
4
Ich bin mit dem letzten Satz der Frage nicht einverstanden. Ein einfacher Boolescher Wert ist hier weitaus sauberer als ein Mutex. Machen Sie es zu einem atomaren Bool, wenn Sie den Master-Mutex nicht sperren möchten, um ein übergeordnetes Element "zurückzugeben".
Sebastian Redl

Antworten:

53

Ich kann mindestens zwei schwerwiegende Probleme mit der vorgeschlagenen Operation feststellen.

Der erste wurde bereits in einem Kommentar von @ gnasher729 erwähnt :

Sie können nicht wirklich vernünftigerweise überprüfen, ob ein Mutex gesperrt ist, da er eine Nanosekunde nach der Überprüfung entsperrt oder gesperrt werden kann. Also , wenn Sie schrieb if (mutex_is_locked ()) …dann mutex_is_lockedkönnte das richtige Ergebnis zurück, sondern durch die Zeit , die ifausgeführt wird, ist es falsch.

Die einzige Möglichkeit, um sicherzustellen, dass sich die Eigenschaft "Ist gesperrt" eines Mutex nicht ändert, besteht darin, sie selbst zu sperren.

Das zweite Problem, das ich sehe, ist, dass Ihr Thread nicht mit dem Thread synchronisiert wird, der zuvor den Mutex gesperrt hatte, es sei denn, Sie sperren einen Mutex. Daher ist es nicht einmal genau definiert, von "vorher" und "nachher" zu sprechen, und ob der Mutex gesperrt ist oder nicht, ist eine Art Frage, ob Schrödigers Katze gerade lebt, ohne zu versuchen, die Schachtel zu öffnen.

Wenn ich das richtig verstehe, sind in Ihrem speziellen Fall beide Probleme fraglich, da der Master-Mutex gesperrt ist. Dies scheint mir jedoch kein besonders häufiger Fall zu sein, weshalb ich der Meinung bin, dass das Komitee das Richtige getan hat, indem es keine Funktion hinzugefügt hat, die in ganz speziellen Szenarien nützlich sein und in allen anderen Fällen Schaden anrichten könnte. (Im Sinne von: „Benutzeroberflächen leicht und falsch zu bedienen machen.“)

Und wenn ich sagen darf, denke ich, dass das Setup, das Sie derzeit haben, nicht das eleganteste ist und überarbeitet werden könnte, um das Problem insgesamt zu vermeiden. Anstatt beispielsweise den Master-Thread zu verwenden, der alle potenziellen Eltern auf eine derzeit nicht gesperrte überprüft, sollten Sie eine Warteschlange mit bereiten Eltern verwalten. Wenn ein Thread einen anderen optimieren möchte, wird der nächste Thread aus der Warteschlange entfernt, und sobald er neue Eltern hat, werden diese der Warteschlange hinzugefügt. Auf diese Weise benötigen Sie nicht einmal den Master-Thread als Koordinator.

5gon12eder
quelle
Danke, das ist eine gute Antwort. Der Grund, warum ich keine Warteschlange für fertige Eltern haben möchte, ist, dass ich die Reihenfolge, in der die Eltern erstellt wurden, beibehalten muss (da dies ihre Lebensdauer bestimmt). Dies ist mit einer LIFO-Warteschlange problemlos möglich. Wenn ich anfange, Dinge hinein- und herauszuziehen, müsste ich einen separaten Ordnungsmechanismus beibehalten, der die Dinge kompliziert, daher der aktuelle Ansatz.
Quant
14
@quant: Wenn Sie zwei Zwecke haben, um Eltern in die Warteschlange zu stellen, können Sie dies mit zwei Warteschlangen tun ....
@quant: Sie löschen ein Element (höchstens) einmal, führen jedoch vermutlich die Verarbeitung mehrmals durch, sodass Sie den seltenen Fall auf Kosten des allgemeinen Falls optimieren. Dies ist selten wünschenswert.
Jerry Coffin
2
Es ist aber sinnvoll zu fragen, ob der aktuelle Thread den Mutex gesperrt hat.
Eingeschränktes Sühnopfer
@LimitedAtonement Nicht wirklich. Dazu muss der Mutex zusätzliche Informationen (Thread-ID) speichern, wodurch er langsamer wird. Rekursive Mutexe tun dies bereits, Sie sollten sie stattdessen verwenden.
StaceyGirl
9

Anscheinend verwenden Sie die sekundären Mutexe nicht, um den Zugriff auf ein Optimierungsproblem zu sperren, sondern um festzustellen, ob ein Optimierungsproblem gerade optimiert wird oder nicht.

Das ist völlig unnötig. Ich hätte eine Liste von Problemen, die optimiert werden müssen, eine Liste von Problemen, die gerade optimiert werden, und eine Liste von Problemen, die optimiert wurden. (Nehmen Sie "Liste" nicht wörtlich, sondern "jede geeignete Datenstruktur").

Der Vorgang des Hinzufügens eines neuen Problems zur Liste nicht optimierter Probleme oder des Verschiebens eines Problems von einer Liste zur nächsten würde unter dem Schutz des einzelnen "Master" -Mutex erfolgen.

gnasher729
quelle
1
Sie glauben nicht, dass ein Objekt vom Typ std::mutexfür eine solche Datenstruktur geeignet ist?
Quant
2
@quant - nein. std::mutexstützt sich auf eine vom Betriebssystem definierte Mutex-Implementierung, die möglicherweise Ressourcen (z. B. Handles) benötigt, die begrenzt sind und deren Zuweisung und / oder Ausführung langsam vonstatten gehen. Die Verwendung eines einzelnen Mutex zum Sperren des Zugriffs auf eine interne Datenstruktur ist wahrscheinlich effizienter und möglicherweise auch skalierbarer.
Periata Breatta
1
Berücksichtigen Sie auch Bedingungsvariablen. Sie können eine Menge solcher Datenstrukturen sehr einfach erstellen.
Cort Ammon - Reinstate Monica
2

Wie andere gesagt haben, gibt es keinen Anwendungsfall, bei dem is_lockedein Mutex von Nutzen ist, weshalb die Funktion nicht existiert.

Der Fall, bei dem Sie ein Problem haben, ist unglaublich häufig. Es ist im Grunde das, was Worker-Threads tun, die eine der, wenn nicht die häufigste Implementierung von Threads sind.

Sie haben ein Regal mit 10 Kartons. Sie haben 4 Arbeiter, die mit diesen Boxen arbeiten. Wie stellen Sie sicher, dass die 4 Arbeiter an verschiedenen Boxen arbeiten? Der erste Arbeiter nimmt eine Kiste aus dem Regal, bevor er damit beginnt, daran zu arbeiten. Der zweite Arbeiter sieht 9 Kartons im Regal.

Es gibt keine Mutexe, um die Boxen zu sperren. Daher ist es nicht erforderlich, den Status des imaginären Mutex auf der Box zu sehen, und es ist einfach falsch, einen Mutex als Booleschen Wert zu missbrauchen. Der Mutex verriegelt das Regal.

Peter
quelle
1

Zusätzlich zu den beiden oben in der Antwort von 5gon12eder genannten Gründen möchte ich hinzufügen, dass dies weder notwendig noch wünschenswert ist.

Wenn Sie bereits einen Mutex in der Hand haben, sollten Sie wissen, dass Sie ihn in der Hand haben! Du musst nicht fragen. Genau wie beim Besitz eines Speicherblocks oder einer anderen Ressource sollten Sie genau wissen, ob Sie den Speicher besitzen oder nicht und wann es angebracht ist, die Ressource freizugeben / zu löschen.
Wenn dies nicht der Fall ist, ist Ihr Programm schlecht konzipiert und Sie steuern auf Probleme zu.

Wenn Sie auf die gemeinsam genutzte Ressource zugreifen müssen, die durch den Mutex geschützt ist, und Sie den Mutex noch nicht besitzen, müssen Sie den Mutex erwerben. Es gibt keine andere Option, sonst ist Ihre Programmlogik nicht korrekt.
In beiden Fällen können Sie eine Blockierung für akzeptabel oder inakzeptabel halten lock()oder try_lock()das gewünschte Verhalten angeben. Alles, was Sie positiv und ohne Zweifel wissen müssen, ist, ob Sie den Mutex erfolgreich erworben haben (der Rückgabewert von try_lockteilt Ihnen mit). Es spielt keine Rolle, ob jemand anderes es hält oder ob Sie einen falschen Fehler haben.

In jedem anderen Fall geht es Sie nichts an. Sie müssen es nicht wissen und sollten es auch nicht wissen oder Annahmen treffen (für die in der anderen Frage genannten Aktualitäts- und Synchronisierungsprobleme).

Damon
quelle
1
Was ist, wenn ich eine Rangfolge für die derzeit zum Sperren verfügbaren Ressourcen durchführen möchte?
Quant
Aber ist das realistisch? Das finde ich eher ungewöhnlich. Ich würde sagen, dass beide Ressourcen bereits eine bestimmte Art von Rang haben, dann müssen Sie zuerst die wichtigere erledigen (die Sperre dafür erwerben). Beispiel: Die Physiksimulation muss vor dem Rendern aktualisiert werden. Oder die Rangfolge ist mehr oder weniger absichtlich, dann können Sie auch try_lockdie erste Ressource, und wenn diese fehlschlägt, versuchen Sie die zweite. Beispiel: Drei dauerhafte, zusammengefasste Verbindungen zum Datenbankserver, und Sie müssen eine verwenden, um einen Befehl zu senden.
Damon
4
@quant - "eine Rangfolgeoperation für die Ressourcen, die derzeit zum Sperren verfügbar sind" - im Allgemeinen ist dies eine sehr einfache und schnelle Methode, um Code zu schreiben, der Deadlocks auf eine Art und Weise verursacht, die Sie nur schwer herausfinden können. In fast allen Fällen ist es die beste Strategie, die Erfassung und Freigabe von Sperren deterministisch zu gestalten. Das Suchen nach einer Sperre basierend auf einem Kriterium, das sich ändern könnte, ist problematisch.
Periata Breatta
@PeriataBreatta Mein Programm ist absichtlich unbestimmt. Ich sehe jetzt, dass dieses Attribut nicht allgemein ist, also kann ich das Weglassen is_locked()solcher Merkmale verstehen , die ein solches Verhalten erleichtern könnten.
Quant
@quant Ranking und Locking sind völlig getrennte Probleme. Wenn Sie eine Warteschlange mit einer Sperre sortieren oder neu anordnen möchten, sperren Sie sie, sortieren Sie sie und entsperren Sie sie. Wenn Sie brauchen is_locked, gibt es eine viel bessere Lösung für Ihr Problem als die, an die Sie denken.
Peter
1

Möglicherweise möchten Sie atomic_flag mit der Standardspeicherreihenfolge verwenden. Es gibt keine Datenrennen und wirft niemals Ausnahmen wie Mutex bei mehreren Aufhebungsaufrufen (und bricht möglicherweise unkontrolliert ab ...). Alternativ gibt es atomic (zB atomic [bool] oder atomic [int] (mit dreieckigen Klammern, nicht [])), das nette Funktionen wie load und compare_exchange_strong hat.

Andrew
quelle
1

Ich möchte dazu einen Anwendungsfall hinzufügen: Er würde eine interne Funktion aktivieren, um als Voraussetzung / Behauptung sicherzustellen, dass der Anrufer tatsächlich die Sperre hält.

Für Klassen mit mehreren solchen internen Funktionen und möglicherweise vielen öffentlichen Funktionen, die sie aufrufen, könnte sichergestellt werden, dass jemand, der eine weitere öffentliche Funktion hinzufügt, die die interne Funktion aufruft, tatsächlich die Sperre erhalten hat.

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();
}

};
B3ret
quelle