Sicherlich haben viele gehört, aber jemand hat sich in der Praxis getroffen, wie zum Beispiel Deadlocks und Rennbedingungen. Diese Konzepte werden als Fehler bei der Verwendung der Parallelität klassifiziert. Wenn ich Ihnen eine Frage zum Deadlock stelle, werden Sie sehr wahrscheinlich ohne Zweifel ein klassisches Deadlock-Bild oder dessen Darstellung im Pseudocode zeichnen. So etwas wie das:

Wir erhalten diese Informationen am Institut, sie sind in Büchern und Artikeln im Internet zu finden. Ein solcher Deadlock, der beispielsweise zwei Mutexe in seiner ganzen Pracht verwendet, kann im Code gefunden werden. In den meisten Fällen ist jedoch nicht alles so einfach, und nicht jeder kann das klassische Fehlermuster im Code erkennen, wenn es nicht in der üblichen Form dargestellt wird.

Stellen Sie sich eine Klasse vor, an der wir an den Methoden StartUpdate, CheckAndUpdate und Stop interessiert sind. C ++ wird verwendet. Der Code ist so einfach wie möglich:
std::recursive_mutex m_mutex; Future m_future; void Stop() { std::unique_lock scoped_lock(m_mutex); m_future.Wait();
Worauf Sie im vorgestellten Code achten sollten:
- rekursiver Mutex wird verwendet. Die wiederholte Erfassung eines rekursiven Mutex führt nicht nur dann zu Erwartungen, wenn diese Erfassung im selben Thread erfolgt. In diesem Fall sollte die Anzahl der Mutex-Ausnahmen der Anzahl der Erfassungen entsprechen. Wenn wir versuchen, einen rekursiven Mutex zu erfassen, der bereits in einem anderen Thread erfasst wurde, wechselt der Thread in den Standby-Modus.
- Die Future :: Schedule-Funktion startet (in n Millisekunden) in einem separaten Thread, an den der Rückruf übergeben wurde
Jetzt analysieren wir alle erhaltenen Informationen und erstellen ein Bild:

Unter Berücksichtigung der beiden oben dargestellten Tatsachen ist es nicht schwer zu schließen, dass ein Versuch, einen rekursiven Mutex in einer der Funktionen zu erfassen, zur Erwartung der Freigabe des Mutex führt, wenn er bereits in einer anderen Funktion erfasst wurde, da der CheckAndUpdate-Rückruf immer in einem separaten Thread ausgeführt wird.
Auf den ersten Blick gibt es nichts Verdächtiges in Bezug auf Deadlock. Genauer gesagt kommt es auf unser klassisches Bild an. Wenn die Ausführung des Funktionsobjekts beginnt, erfassen wir implizit die Ressource m_future, den Rückruf direkt
verbunden mit m_future:
Die Reihenfolge der Aktionen, die zum Deadlock führen, ist wie folgt:- Es ist geplant, CheckAndUpdate auszuführen, aber der Rückruf wird nicht sofort nach n Millisekunden gestartet.
- Die Stop-Methode wird aufgerufen und dann gestartet: Wir versuchen, den Mutex zu erfassen - die Ressource ist eine erfasste, wir beginnen zu warten, bis die m_future abgeschlossen ist - das Objekt wurde noch nicht aufgerufen, wir warten.
- Die Ausführung von CheckAndUpdate beginnt: Wir versuchen, den Mutex zu erfassen - wir können nicht, die Ressource wird bereits von einem anderen Thread erfasst, wir warten auf die Veröffentlichung.
Das ist alles: Der Thread, der den Stop-Aufruf ausführt, wartet auf den Abschluss von CheckAndUpdate, und der andere Thread kann seinerseits nicht weiterarbeiten, bis er den Mutex erfasst, der bereits vom zuvor erwähnten Thread erfasst wurde. Es ist ein ziemlich klassischer Deadlock. Die Hälfte der Arbeit ist erledigt - die Ursache des Problems wurde entdeckt.
Nun ein wenig darüber, wie man es behebt.Ansatz 1Das Verfahren zum Erfassen von Ressourcen sollte dasselbe sein, um Deadlocks zu vermeiden. Das heißt, Sie müssen prüfen, ob es möglich ist, die Reihenfolge der Erfassung von Ressourcen in der Stop-Methode zu ändern. Da hier der Deadlock-Fall nicht ganz offensichtlich ist und die Ressource m_future in CheckAndUpdate nicht explizit erfasst wird, haben wir uns entschlossen, über eine andere Lösung nachzudenken, um die Fehlerrückgabe in Zukunft zu vermeiden.
Ansatz 2- Überprüfen Sie, ob Sie die Verwendung des Mutex in CheckAndUpdate deaktivieren können.
- Da wir den Synchronisationsmechanismus verwenden, beschränken wir den Zugriff auf einige Ressourcen. Vielleicht reicht es für Sie aus, diese Ressourcen (wie wir) in Atomics umzuwandeln, auf die der Zugriff bereits threadsicher ist.
- Es stellte sich heraus, dass Variablen, auf die der Zugriff beschränkt war, leicht in Atomics konvertiert werden können, sodass der erwähnte Mutex erfolgreich gelöscht wird.
Hier ist ein so einfaches Beispiel mit einem nicht offensichtlichen Deadlock, der sich leicht auf das Muster dieses Fehlers reduziert. Abschließend möchte ich, dass Sie zuverlässigen und thread-sicheren Code schreiben!