Im folgenden Beispiel (ein idealisiertes "Spiel") gibt es zwei Threads. Der Haupt-Thread, der Daten und RenderThread
aktualisiert, was ihn auf dem Bildschirm "rendert". Was ich brauche sind diese beiden um synchronisiert zu werden. Ich kann es mir nicht leisten, mehrere Update-Iterationen auszuführen, ohne dass für jede einzelne ein Rendering ausgeführt wird.
Ich benutze condition_variable
, um diese beiden zu synchronisieren, also wird der schnellere Thread im Idealfall einige Zeit auf das langsamere warten. Zustandsvariablen scheinen jedoch die Aufgabe nicht zu erfüllen, wenn einer der Threads eine Iteration für eine sehr kurze Zeit ausführt. Es scheint schnell die Sperre des Mutex wiederzuerlangen, bevor wait
im anderen Thread es erfassen kann. Auch wenn notify_one
heißt
Hier ist die Quelle dieses kleinen Beispielcodes , und wie Sie sehen können, läuft auch bei Ideone der Ausgabe ist frame 0: 19
(das bedeutet, dass der Render-Thread eine einzelne Iteration abgeschlossen hat, während der Update-Thread alle 20 davon abgeschlossen hat).
Wenn wir Zeile 75 auskommentieren (dh einige Zeit für die Update-Schleife simulieren), läuft alles gut. Jeder Aktualisierungsiteration ist eine Renderiteration zugeordnet.
Gibt es eine Möglichkeit, diese Threads tatsächlich wirklich zu synchronisieren, selbst wenn eine von ihnen eine Iteration in wenigen Nanosekunden abschließt, aber auch ohne eine Leistungseinbuße, wenn sie beide einige Millisekunden benötigen?
Wenn ich es richtig verstehe, möchten Sie, dass die zwei Threads abwechselnd arbeiten: Updater warten bis der Renderer fertig ist, um erneut zu iterieren, und der Renderer wartet bis der Updater beendet ist, um erneut zu iterieren. Ein Teil der Berechnung könnte parallel sein, aber die Anzahl der Iterationen sollte zwischen beiden ähnlich sein.
Sie benötigen 2 Sperren:
Updater:
%Vor%Renderer:
%Vor%BEARBEITET:
Auch wenn es einfach aussieht, gibt es mehrere Probleme zu lösen:
Zulassen, dass ein Teil der Berechnungen parallel durchgeführt wird: Wie im obigen Snippet werden Aktualisierung und Rendern nicht parallel, sondern sequenziell sein, so dass Multithread nicht von Vorteil ist. Um eine echte Lösung zu finden, sollte die Berechnung vor dem Warten durchgeführt werden, und nur die Kopie der neuen Werte muss zwischen dem Warten und dem Signal liegen. Das Gleiche gilt für das Rendering: Alle Rendervorgänge müssen nach dem Signal ausgeführt werden und nur den Wert zwischen dem Warten und dem Signal erhalten.
Die Implementierung muss sich auch um den Anfangszustand kümmern: So wird vor dem ersten Update kein Rendering durchgeführt.
Die Beendigung beider Threads: So wird niemand gesperrt bleiben oder endlos schleifen, nachdem der andere beendet ist.
Ich denke, ein Mutex (alleine) ist nicht das richtige Werkzeug für den Job. Vielleicht möchten Sie stattdessen einen Semaphor (oder etwas Ähnliches) verwenden. Was Sie beschreiben, klingt sehr nach einem Producer / Consumer-Problem , dh ein Prozess darf nur einmal ausgeführt werden Prozess hat eine Aufgabe abgeschlossen. Daher können Sie sich auch die Erzeuger- / Verbrauchermuster ansehen. Zum Beispiel könnte diese Serie Ihnen einige Ideen geben:
Dort wird ein std::mutex
mit einem std::condition_variable
kombiniert, um das Verhalten eines Semaphors nachzuahmen. Ein Ansatz, der ganz vernünftig erscheint. Sie würden wahrscheinlich nicht hoch- und runterzählen, sondern stattdessen true und false für eine Variable mit netraw -Semantik schalten.
Als Referenz:
Eine Technik, die oft in Computergrafiken verwendet wird, ist die Verwendung eines Doppelpuffers. Anstatt dass der Renderer und der Produzent dieselben Daten im Speicher bearbeiten, hat jeder seinen eigenen Puffer. Dies wird implementiert, indem zwei unabhängige Puffer verwendet werden, und diese bei Bedarf wechseln. Der Erzeuger aktualisiert einen Puffer, und wenn er fertig ist, schaltet er den Puffer um und füllt den zweiten Puffer mit den nächsten Daten. Jetzt, während der Producer den zweiten Puffer verarbeitet, arbeitet der Renderer mit dem ersten und zeigt ihn an.
Sie können diese Technik verwenden, indem Sie den Renderer die Auslagerungsoperation so sperren lassen, dass der Produzent warten muss, bis das Rendering beendet ist.
Dies liegt daran, dass Sie eine separate Variable drawing
verwenden, die nur dann gesetzt wird, wenn der Rendering-Thread den Mutex nach einem wait
erneut anfordert, was möglicherweise zu spät ist. Das Problem verschwindet, wenn die Variable drawing
entfernt wird und die Überprüfung für wait
im Update-Thread durch ! m_rt.readyToDraw
ersetzt wird (was bereits vom Update-Thread festgelegt wurde und daher nicht für das logische Rennen anfällig ist.
Geänderter Code und Ergebnisse
Da die Threads nicht parallel arbeiten, habe ich nicht wirklich den Sinn, zwei Threads zu haben. Es sei denn, Sie sollten später eine doppelte (oder sogar dreifache) Pufferung implementieren.
Tags und Links c++ multithreading synchronization