In C ++ 14 scheint ein Mechanismus zur Überprüfung, ob ein std::mutex
gesperrt 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.
is_locked
?Antworten:
Ich kann mindestens zwei schwerwiegende Probleme mit der vorgeschlagenen Operation feststellen.
Der erste wurde bereits in einem Kommentar von @ gnasher729 erwähnt :
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.
quelle
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.
quelle
std::mutex
für eine solche Datenstruktur geeignet ist?std::mutex
stü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.Wie andere gesagt haben, gibt es keinen Anwendungsfall, bei dem
is_locked
ein 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.
quelle
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()
odertry_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 vontry_lock
teilt 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).
quelle
try_lock
die 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.is_locked()
solcher Merkmale verstehen , die ein solches Verhalten erleichtern könnten.is_locked
, gibt es eine viel bessere Lösung für Ihr Problem als die, an die Sie denken.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.
quelle
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.
quelle