Synchronisieren sehr schneller Threads

8

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

%Vor%

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?

    
Borislav Stanimirov 08.10.2015, 09:09
quelle

4 Antworten

3

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:

  • eine für die Aktualisierung
  • eine für das Rendering

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.

    
Adrian Maire 08.10.2015, 09:40
quelle
2

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:

moooeeeep 08.10.2015 09:40
quelle
1

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.

    
Jens 08.10.2015 11:11
quelle
1

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.

    
Arne Vogel 08.10.2015 14:32
quelle