Effiziente Synchronisation von Threads

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Effiziente Synchronisation von Threads

Beitrag von Horus »

Hi,

ich wollte mal eure Meinungen zu meiner Synchronisationsklasse einholen.

Kann man da noch was drehen, damit das Ding noch schneller wird?
Kann es zu Deadlocks kommen? (Im Test gab es bisher noch keine)

So sieht die Klasse aus:
http://pastebin.com/ajVSe1La

So wird sie genutzt:
http://pastebin.com/0e36niaq
void ParallelFunction() wird dabei von mehreren Threads ausgeführt, diese werden Synchronisiert.
Immer bei BarrierObj.Set() wird auf alle Threads gewartet.

Meine Methode ist um ein vielfaches Schneller als Synchronisation mit der condition_variable.
http://en.cppreference.com/w/cpp/thread ... n_variable


Aber wie gesagt, ich hoffe, dass es sogar noch ein bisschen besser geht?

MFG
Benutzeravatar
Schrompf
Moderator
Beiträge: 4859
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Schrompf »

Yield-Schleifen sind übel für das Betriebssystem. Wenn grad nix zu tun ist, kriegt der Nutzer damit ein ganzes Bündel hässlicher Effekte, schlimmstenfalls die Rückkehr der guten alten sprunghaften Maus. std::thread und Konsorten dürften dagegen OS-Befehle nutzen, damit die Threads wirklich schlafen und in der Mikrosekunde aufgeweckt werden, in der sie gebraucht werden. Wenn Du ein Spiel schreibst, was auf dem kleinsten Laptop und auf dem größten Zocker-PC immer alle Resourcen fressen soll, sind Busy Loops dieser Art schon ok und durchaus auch schneller als die OS-kompatiblen Pfade. Es ist aber zumindest schlechter Stil.

Abgesehen davon tut Dein Beispiel nix paralleles, daher kann ich den Nutzen der Sache nicht beurteilen. Und auch nicht beurteilen, ob Dein "vielfach schneller" tatsächlich in der Realität auftritt oder nur den bevorzugten Messmethoden zuzuschreiben ist. Ich erinnere mich noch gut an die Diskussion im sppro.de zu den template-Optimierungspfaden. Ich werde meine Zeit nicht für noch eine Diskussion dieser Art verschwenden.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Yield-Schleifen sind übel für das Betriebssystem. Wenn grad nix zu tun ist, kriegt der Nutzer damit ein ganzes Bündel hässlicher Effekte, schlimmstenfalls die Rückkehr der guten alten sprunghaften Maus.
Ok, danke, das mit dem Yield nehme ich mir nochmal vor.
SuspendThread und ResumeThread vielleicht?
Ich werde meine Zeit nicht für noch eine Diskussion dieser Art verschwenden.
Was :o

LG
Benutzeravatar
Krishty
Establishment
Beiträge: 8248
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Krishty »

Hmmm; das gilt aber nur für Spinlocks der Art while(!var) { } oder while(!var) { __noop(); }. Ich erinnere mich, dass x86 einen speziellen Spinlock-Befehl hat, den die CPU auch als „warte und schieb andere Hyperthreads dazwischen; pumpe nicht deine Pipeline voll; schalte auf Energiesparen bis sich eine Cache-Zeile ändert“ versteht. Falls Yield damit arbeitet, ist es tatsächlich in Ordnung.

Nachtrag: Die Anweisung ist pause. Schau also im Disassembly nach, ob derYield-Aufruf ein pause-Mnemonic erzeugt. Falls du es von Hand machen willst und Visual C++ benutzt, kannst du das YieldProcessor()-Makro oder _mm_pause() aus <Windows.h> benutzen.

Ansonsten habe ich mir den Quelltext nicht weiter angeschaut weil ich nicht wirklich begriffen habe, was er tun soll.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Hi,
Ich erinnere mich, dass x86 einen speziellen Spinlock-Befehl hat, den die CPU auch als „warte und schieb andere Hyperthreads dazwischen;

Ok das werde ich prüfen, falls es nicht der Befehel ist nehme ich den und prüfe mal die Update-rate.
Ansonsten habe ich mir den Quelltext nicht weiter angeschaut weil ich nicht wirklich begriffen habe, was er tun soll.
der Code synchronisiert eine Funktion die von mehreren Threads Ausgeführt wird.
Immer wenn die Barriere erreicht wird warten alle Threads auf den letzten, dann erst geht es weiter.
Bis zu nächsten Barriere. Usw...

In meiner Zielanwendung müssen Rechenschritte streng schrittweise, parallel ausgeführt werden, es darf natürlich keine "race conditions" geben. Bisher hatte ich dort concurrency::parallel_for genutzt.


Nebenbei habe ich das ganze mal mit SuspendThread und ResumeThread probiert.
Es geht sehr schnell und ist sehr labil, kommt leicht zu "race conditions".
Leider gehts also nicht, der Performance-Gewinn zeigt aber wie viel durch yield momentan verloren geht.

LG
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Effiziente Synchronisation von Threads

Beitrag von Spiele Programmierer »

Ich glaube nicht das Yield intern diesen Befehl verwendet.
void yield();
Provides a hint to the implementation to reschedule the execution of threads
"Pause" heißt, den Kern (kurz) warten lassen; "Yield" heißt, anderen Thread rechnen lassen.
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Also, Yield nimmt in der Tat nicht _mm_pause()

Nach dem gestrigen Prinzip habe ich einen Threadpool implementiert und noch weiter getestet.
ThreadPool.h
http://pastebin.com/4JJk9qs8

Und der Test:
http://pastebin.com/QpVnh1zz

In Zeile 38 des Threadpool habe ich folgende probiert:
std::this_thread::yield();
Sleep(0);
_mm_pause();
std::this_thread::sleep_for(std::chrono::milliseconds(0));

Bis auf _mm_pause(); sind alle gleich auf. _mm_pause(); bricht die Performance um 95% ein.
Das ist das erste ein Rätsel...

Da ich grundsätzlich vermutete, dass zu viele Threads alles bremsen habe ich mal die Performance bei unterschiedlicher Thread-anzahl getestet (Ich hab einen Dualcore):
Unbenannt.png
Anscheinend ich dem nicht so. Das ist das 2. Rätsel...
Vorteilhaft ist bei der Implementierung vor allem eine gerade Thread-anzahl :lol: Toll.

Das Thema ist komplex, an dem Threadpool werde ich noch was zu drehen versuchen, oder ist ein ganz anderer Ansatz optimal?

MFG


Nachtrag:
Unbenannt2.png
Wenn man es einstellt, dass die die Synchonisationsfunktionen überrepräsentiert sind (also sehr kleine Arbeitspakete nach jedem wird synchonisiert) dann zeigt der Profiler wo der Preis für die Synchonisation gezahlt wird :(
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Effiziente Synchronisation von Threads

Beitrag von Spiele Programmierer »

Mich wundert es kaum, das die Performance sich da so stark unterscheidet.
Das "std"-Sleep ist mit 99% Wahrscheinlichkeit bloß auf das WinAPI "Sleep" gewrappt.
"Yield" ist von der Funktionalität zumindest theoretisch mehr oder weniger äquivalent mit dem "Sleep(0)", es könnte aber schon sein, das es einen andere Betriebssystemsaufruf nutzt.
Normalerweise verwendet bei der WinAPI um das zu erreichen scheinabar aber auch häufig "Sleep(0)".

"Pause" macht etwas völlig anderes.
Pause ist Teil von SSE2 und bremst den Prozessor für Busy Waits(Spinloops) etwas, ohne mehr Rechenkapazität und Strom zu verschwenden als nötig ist und sollte dafür eine leicht schonendere Umsetzung erlauben.

Am Wichtigsten ist es lange von kurzen Pausen zu unterscheiden und je nachdem die Zeit abzugeben(was nicht ohne Overhead geht) oder die Kontrolle zu behalten und nur kurz in einen Busy-Wait zu gehen, um den Context Switch und Desynchronisationsoverhead zu vermeiden, der bei der Abgabe der Zeit an andere Threads entstehen würde. ("Sleep" & "Yield")

Anstatt "Sleep" und "Yield" für die längeren Rechenpausen, würde ich mir allerdings mal lieber nochmal andere Ansätze anschauen wie das Suspend/Resume, Condition Variablen etc. Damit betreibst du sonst rund um die Uhr Polling und Context Switches in den Thread und gleich wieder zurück, weil der Thread ja doch noch nichts zum Rechnen hat. Nicht sehr effizient.
Benutzeravatar
Krishty
Establishment
Beiträge: 8248
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Krishty »

Das sieht aus als ob du zu viele Threads für deinen Zweikerner erzeugst. Nicht jeder Thread hat seinen eigenen CPU-Kern sondern ist darauf angewiesen dass irgendwann ein Thread seine CPU abgibt (Yield) damit der Thread, der sonst keine CPU-Zeit abbekäme, laufen kann.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Hi,
"Pause" macht etwas völlig anderes.
Pause ist Teil von SSE2 und bremst den Prozessor für Busy Waits(Spinloops) etwas, ohne mehr Rechenkapazität und Strom zu verschwenden als nötig ist und sollte dafür eine leicht schonendere Umsetzung erlauben.
Ich werde das mal in meinem Projekt probieren. Da gibt es Stellen wo sehr kurz gelockt werden muss.
Für die Synchonisation des Threadpool sind die Wartezeiten wohl manchmal zu lang für _mm_pause().
Am Wichtigsten ist es lange von kurzen Pausen zu unterscheiden
Mal sehen ob das gelingt.
andere Ansätze anschauen wie das Suspend/Resume, Condition Variablen etc.
ResumeThread() und SuspendThread() scheinen prinzipiell gut geeignet. Leider ist es mit damit noch nicht gelungen.
Das sieht aus als ob du zu viele Threads für deinen Zweikerner erzeugst.
Nun ich habe von 1 bis 16 durchprobiert und die Updaterate aufgezeichnet.
Auf meinem Dualcore scheinen 4 Threads geeignet.
Erstaunlicher Weise ist es im Test mit 16 Threads kaum langsamer gewesen.


Nachtrag:
Hier jetzt eine funktionsfähige Variante mit ResumeThread() und SuspendThread().
http://pastebin.com/F4NzxWaB

Jeder Thread der ankommt "Suspended" den vorherigen.
Der letzte wecke alle schlafenden.
Schneller ist es nicht.
Das Warten verteilt sich nur anders^^
Unbenannt3.png
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Helmut »

ResumeThread() und SuspendThread() sind nicht zur Synchronisierung gedacht und sollten auch nicht dafür verwendet werden. Warum nutzt du nicht einfach CriticalSections? Wenn die zu langsam werden gibt es noch andere Tricks, aber erstmal sollte man die verwenden.
Auch verstehe ich wie Schrompf deinen Code nicht. Nirgendswo wird irgendeine Arbeit gemacht.
Benutzeravatar
Krishty
Establishment
Beiträge: 8248
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Krishty »

Er meint mit Synchronisierung wohl mehr sowas wie WaitForMultipleObjects(ALL), glaube ich … jedenfalls arbeitet das Konzept im Augenblick so.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

ResumeThread() und SuspendThread() sind nicht zur Synchronisierung gedacht und sollten auch nicht dafür verwendet werden.
Das ist mir bekannt, jedoch erscheint das Konzept sinnvoll, um mehrere Threads die warten müssen aus dem "busy wait" zu bekommen.
Ich glaube "CriticalSections" helfen mir nicht bei "warten lassen" von Threads auf einander.
Auch verstehe ich wie Schrompf deinen Code nicht. Nirgendswo wird irgendeine Arbeit gemacht.
Ich habe nochmal ein vereinfachtes Beispiel von dem Einsatz meines ThreadPool-Entwurfes.
http://pastebin.com/13TRyd2K
Er meint mit Synchronisierung wohl mehr sowas wie WaitForMultipleObjects(ALL), glaube ich … jedenfalls arbeitet das Konzept im Augenblick so.
+
Das mag sein. Ich werde mir WaitForMultipleObjects(ALL) ansehen, das kenne ich nicht.

LG
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von dot »

Horus hat geschrieben:
andere Ansätze anschauen wie das Suspend/Resume, Condition Variablen etc.
ResumeThread() und SuspendThread() scheinen prinzipiell gut geeignet. Leider ist es mit damit noch nicht gelungen.
Nein, die sind überhaupt nicht für sowas geeignet, steht sogar extra der Hinweis in der MSDN:
MSDN hat geschrieben:This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. [...]
;)
Horus hat geschrieben:
ResumeThread() und SuspendThread() sind nicht zur Synchronisierung gedacht und sollten auch nicht dafür verwendet werden.
Das ist mir bekannt, jedoch erscheint das Konzept sinnvoll, um mehrere Threads die warten müssen aus dem "busy wait" zu bekommen.
Ich glaube "CriticalSections" helfen mir nicht bei "warten lassen" von Threads auf einander.
Schau dir mal Condition Variablen, Events, I/O Completion Ports etc. an.
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Helmut »

Versteh ich das richtig, dass alle Workerthreads gleichzeitig anfangen zu arbeiten und der Mainthread darauf wartet dass alle fertig werden und dann wieder alle Workerthreads mit was neuem anfangen?
Die einfachste Lösung wäre dann tatsächlich WaitForMultipleObjects. Dann musst du die Threads aber für jede Aufgabe neu erstellen. Alternativ könntest du in 'nem Zähler, den du atomar inkrementierst, speichern, wie viele Threads fertig sind und dann im letzten Thread den Mainthread über ein Event bescheid geben, dass alle Threads fertig sind. Der Mainthread kann dann über ein zweites Event wieder alle Threads aufwecken. Siehe dazu in der MSDN nach CreateEvent, SetEvent und WaitForSingleObject.
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Nein, die sind überhaupt nicht für sowas geeignet, steht sogar extra der Hinweis in der MSDN
Das ist mir im Grunde bekannt. Eine Begründung findet sich nicht so richtig.
Vermutlich, weil es schnell zu gemeinen Fehlern kommt, da man nicht prüfen kann ob ein Thread schon suspendet ist, wenn er sich selber suspendet.
In meinem Beispiel geht es nur weil je ein anderer Thread das suspend übernimmt.
Schau dir mal Condition Variablen, Events, I/O Completion Ports etc. an.
Ok.

Versteh ich das richtig, dass alle Workerthreads gleichzeitig anfangen zu arbeiten und der Mainthread darauf wartet dass alle fertig werden und dann wieder alle Workerthreads mit was neuem anfangen?
Jein.
Es gibt keinen "Mainthread" der in die Synchronisation eingreift. Er startet die "nur".
Die Workthreads laufen alle bis zum Threads.Sync().
Der erste der eintrifft wartet, dann legt er weitere schlafen, die eintreffen. Man könnte diesen vorübergehend als Mainthread bezeichnen, jedoch kann dieser bei der nächsten Threads.Sync(). wechseln.
Wenn der letzte eintrifft gehen alle weiter bis zum nächsten Threads.Sync().

Das ganze geht endlos so weiter, bzw bis man es von außen stoppt.
Die einfachste Lösung wäre dann tatsächlich WaitForMultipleObjects. Dann musst du die Threads aber für jede Aufgabe neu erstellen.
Ich habe in der Zielanwendung etwa 1000 Updates/Sekunde. Für jedes Update erwarte ich, dass 3 Mal Threads.Sync(). benötigt wird.

Neu erstellen von Threads kommt bei einer solchen Frequenz nicht in Frage :(
Der Mainthread kann dann über ein zweites Event wieder alle Threads aufwecken.
Wenn ich es so mache, wie schlafen sie?
SuspendThread() und busy wait habe ich ja auch probiert.

Die verschiedenen Methoden zur Synchonisation machen ca 5-10% der Gesamtrechenzeit der Zielanwendung(Videos unten) aus.
Ich finde dass ist viel. Ist natürlich auch der hohen Arbeitsfrequenz geschuldet.
Benutzeravatar
Krishty
Establishment
Beiträge: 8248
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Krishty »

1. Wenn die MSDN sagt, dass du es nicht tun sollst, dann TU ES EINFACH NICHT.

2. Erklärung: The Old New Thing – Why you should never suspend a thread.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Helmut »

Horus hat geschrieben:
Der Mainthread kann dann über ein zweites Event wieder alle Threads aufwecken.
Wenn ich es so mache, wie schlafen sie?
Auf ein Event wartest du mit WaitForSingleObject. Die Funktion habe ich ja auch schon erwähnt.
Warum SuspendThread böse ist kannst du hier nachlesen. Und dass busy wait nicht performant ist, ist wohl klar.
Vielleicht erklärst du uns mal, was du eigentlich machen willst. Ich versteh weder deinen Code noch deine Erklärungen dazu.
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Also,

ich habe es mit dem WaitForSingleObject() probiert.
In meinem Anwendungsfall funktioniert es nicht, bzw. ist nicht Threadsicher.

http://pastebin.com/4dHaVS64

Zwischen Zeile 15 und 21 kann ein Thread einen anderen überholen.
Locks nutzen nichts, ich müsste ja unlocken, nachdem Threads WaitForSingleObject() erreicht haben.

Oder nutze ich das Event verkehrt?

LG
gdsWizard
Establishment
Beiträge: 237
Registriert: 04.02.2005, 09:12
Benutzertext: www.gamedevstudio.com
Echter Name: Thomas Mittelsdorf
Wohnort: Meiningen
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von gdsWizard »

Also ich verwende für Multithreading sogenannte MessagePorts. Sie haben auf dem Amiga schon gute Dienste geleistet und tun dies auch unter Windows. Leider kennt Windows keine MessagePorts mehr so das man sich diese selber schreiben muß. Ein MessagePort ist eine Struktur die ein Signal/Event, ein Mutex und eine Liste mit den Jobs beinhaltet. Das Mutex regelt den Zugriff auf die Liste und das Event zeigt an ob Aufträge in der Liste sind. Alle Threads im Threadpool warten dann einfach auf Aufträge. Dieses Verfahren eignet sich auch für unterschiedliche Aufträge an den Threadpool.
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Helmut »

Dass ist nett, dass du ein minimales Beispiel gemacht hast.
Du brauchst allerdings mindestens zwei Events. Die Racecondition hast du ja schon selber erkannt. Ohne Mainthread ist das etwas tricky, aber es geht so:

Code: Alles auswählen

HANDLE TestEvent1, TestEvent2;
bool TestRunning1 = false;

std::atomic<int> ThreadCounter = 0;

void WaitForAllThreads()
{
    if (ThreadCounter.fetch_add(1) == THREADCOUNT - 1)
    {
        ThreadCounter = 0;
        ResetEvent(TestRunning1 ? TestEvent1 : TestEvent2);
        TestRunning1 = !TestRunning1;
        SetEvent(TestRunning1 ? TestEvent1 : TestEvent2);
    }
    else
        WaitForSingleObject(TestRunning1 ? TestEvent2 : TestEvent1, INFINITE);
}

...

int main()
{
    TestEvent1 = CreateEvent(
            NULL,               // default security attributes
            TRUE,               // manual-reset event
            FALSE,              // initial state is nonsignaled
            NULL                // object name
            );
    TestEvent2 = CreateEvent(
            NULL,               // default security attributes
            TRUE,               // manual-reset event
            FALSE,              // initial state is nonsignaled
            NULL                // object name
            );
    ...
}

 
Performanter kann man so ein Problem nicht lösen. Das Problem ist aber sehr komisch und mich würde interessieren, wozu du sowas brauchst. Du musst auch bedenken, dass sobald ein Thread länger braucht als die anderen er alle blockieren wird.
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Helmut »

Ups, da war noch 'ne kleine Racecondition drin. Hier korrigiert:

Code: Alles auswählen

HANDLE TestEvent1, TestEvent2;
bool TestRunning1 = false;

std::atomic<int> ThreadCounter = 0;

void WaitForAllThreads()
{
    HANDLE NextEvent = TestRunning1 ? TestEvent2 : TestEvent1;
    if (ThreadCounter.fetch_add(1) == THREADCOUNT - 1)
    {
        ThreadCounter = 0;
        ResetEvent(TestRunning1 ? TestEvent1 : TestEvent2);
        TestRunning1 = !TestRunning1;
        SetEvent(TestRunning1 ? TestEvent1 : TestEvent2);
    }
    else
        WaitForSingleObject(NextEvent, INFINITE);
}

...

int main()
{
    TestEvent1 = CreateEvent(
            NULL,               // default security attributes
            TRUE,               // manual-reset event
            FALSE,              // initial state is nonsignaled
            NULL                // object name
            );
    TestEvent2 = CreateEvent(
            NULL,               // default security attributes
            TRUE,               // manual-reset event
            FALSE,              // initial state is nonsignaled
            NULL                // object name
            );
    ...
}
 
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Dass ist nett, dass du ein minimales Beispiel gemacht hast.
Danke, das Kompliment kann ich nur zurückgeben.
Du brauchst allerdings mindestens zwei Events. Die Racecondition hast du ja schon selber erkannt. Ohne Mainthread ist das etwas tricky, aber es geht so:
Die Lösung ist echt cool.
Performanter kann man so ein Problem nicht lösen.
Ich werde die Performance in der Zielanwendung testen. Im Vergleich mit der jetzigen Lösung mit mehreren concurrency::parallel_for() bin ich zuversichtlich, dass es mit den Events überlegen ist.
Du musst auch bedenken, dass sobald ein Thread länger braucht als die anderen er alle blockieren wird
Die Threads holen sich Arbeitspakete ab, diese sind relativ klein. So bekomme ich das bisher in den Griff.
Das Problem ist aber sehr komisch und mich würde interessieren, wozu du sowas brauchst.
Ich arbeite seit längerem an einem "seltsamen" Computerspiel.
https://www.youtube.com/watch?v=zS95WIw ... d6RXN5IteB
Die Engine ist mittlerweile ein echtes Kraftpaket.
Ich habe viel Zeit in Optimierung, SIMD, Datenorientierung usw. gesteckt. Tatsächlich kann man sagen für dieses Projekt habe ich C++ gelernt.
Um alle richtig flüssig zu simulieren sind Updatefrequenzen um die 1000Hz in der Engine nötig.
50.000 und komplexen Partikeln und mehr, bei Modernen CPUs, trotz der hohen Frequenz kein Problem.

Die Verarbeitung erfolgt so:
1. Parallel: Alle Partikel einzeln updaten
2. Parallel: Alle Kollisionen suchen und abarbeiten
3. Ein Thread: Partikel zufügen, entfernen und umsortieren
Dann zurück zu 1.

Ich habe das Beispiel nochmal an meinen Anwendungsfall angepasst, fertig zum kompilieren:

Code: Alles auswählen

#include <windows.h>
#include <thread>
#include <atomic>

#define THREADCOUNT 4

std::thread Threads[THREADCOUNT];
HANDLE TestEvent1, TestEvent2;

std::atomic<size_t> ThreadCounter = 0;

bool TestRunning1 = true;

size_t WaitForAllThreads()
{
	const size_t Count = ThreadCounter.fetch_add(1);
	HANDLE NextEvent = TestRunning1 ? TestEvent2 : TestEvent1;
	if (Count == THREADCOUNT - 1)
	{
		ThreadCounter = 0;
		ResetEvent(TestRunning1 ? TestEvent1 : TestEvent2);
		TestRunning1 = !TestRunning1;
		SetEvent(TestRunning1 ? TestEvent1 : TestEvent2);
	}
	else
		WaitForSingleObject(NextEvent, INFINITE);
	return Count;
}
bool Run = true;

void ThreadFunction(const size_t Id)
{
	while (Run)
	{
		printf("step 0\n");
		WaitForAllThreads();
		printf("step 1\n");
		if (WaitForAllThreads() == 0) // pick only one thread
			printf("sort \n");
		WaitForAllThreads();
	}
}

int main()
{
	TestEvent1 = CreateEvent(
		NULL,               // default security attributes
		TRUE,               // manual-reset event
		FALSE,              // initial state is nonsignaled
		NULL                // object name
		);
	TestEvent2 = CreateEvent(
		NULL,               // default security attributes
		TRUE,               // manual-reset event
		FALSE,              // initial state is nonsignaled
		NULL                // object name
		);

	for (size_t i = 0; i < THREADCOUNT; ++i)
		Threads[i] = std::thread(&ThreadFunction, i);

	getchar();
	bool Run = false;

	for (size_t i = 0; i < THREADCOUNT; ++i)
		Threads[i].join();

	getchar();
}

Vielen Dank auf jeden Fall!
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Helmut »

Kein Problem.
Allerdings solltest du noch die ersten beiden Zeilen in WaitForAllThreads() vertauschen, um ne Racecondition beim Zugriff auf TestRunning1 zu vermeiden. Du solltest vielleicht auch noch TestRunning1 volatile markieren, damit der Compiler das nicht selber vertauscht.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Effiziente Synchronisation von Threads

Beitrag von Spiele Programmierer »

Du solltest vielleicht auch noch TestRunning1 volatile markieren [...]
Dafür gibt es doch Atomics oder insbesondere "std::memory_order". "volatile" finde ich etwas unellegant, weil es das nicht genau aussagt.
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Helmut »

Ja, man sollte wohl wirklich aus dem bool ein std::atomic<bool> machen. Aber solange man Visual Studio verwendet reicht auch volatile.
Gibt's zu Win32 Events eigentlich auch ein Pendant in der Standard Bibliothek?
Horus
Beiträge: 12
Registriert: 14.05.2014, 12:50
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Horus »

Ich habe die oben erläuterte Methode in meiner Zielanwendung ausprobiert und die maximale Updatefrequent mit derjenigen verglichen, welche durch Einsatz zweier concurrency::parallel_for erzielt wurde.

Ich konnte im Rahmen der Ablesegenauigkeit keinen Unterschied feststellen.
Natürlich ziehe ich die neue Lösung mit den Events vor, da ich so mehr Kontrolle habe und ggf. noch was anpassen kann.

Die Threads verarbeiten die Partikel in Paketen. Ich denke ich kann hier noch was rausholen, wenn ich die ersten Pakete groß mache, die letzten klein.
So hab ich zu Beginn der Verarbeitung wenige Brüche in der Cacheline und am Ende sind alle Threads relativ gleichzeitig fertig, weniger Warten ist nötig.
Aber das ist ja nun ein total anderes Thema...
Ja, man sollte wohl wirklich aus dem bool ein std::atomic<bool> machen.
OK mache ich.
Gibt's zu Win32 Events eigentlich auch ein Pendant in der Standard Bibliothek?
Oh das wüsste ich auch sehr gerne.

MFG
Benutzeravatar
Krishty
Establishment
Beiträge: 8248
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Krishty »

Horus hat geschrieben:Ich arbeite seit längerem an einem "seltsamen" Computerspiel.
https://www.youtube.com/watch?v=zS95WIw ... d6RXN5IteB
Die Engine ist mittlerweile ein echtes Kraftpaket.
Ich habe viel Zeit in Optimierung, SIMD, Datenorientierung usw. gesteckt. Tatsächlich kann man sagen für dieses Projekt habe ich C++ gelernt.
Um alle richtig flüssig zu simulieren sind Updatefrequenzen um die 1000Hz in der Engine nötig.
50.000 und komplexen Partikeln und mehr, bei Modernen CPUs, trotz der hohen Frequenz kein Problem.
Ich hatte vergessen zu sagen, dass das fantastisch aussieht :-)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Aramis »

Ja, man sollte wohl wirklich aus dem bool ein std::atomic<bool> machen. Aber solange man Visual Studio verwendet reicht auch volatile.
Es ist weniger Visual Studio als der Umstand, das die x86 und die AMD64-Architektur kein relevantes Memory-Reordering durchfuehren und volatile somit ausreichend ist, da es immerhin alle compiler-generierten Umordnungen verhindert.
Ich hatte vergessen zu sagen, dass das fantastisch aussieht :-)
Dem schliesse ich mich an. Sieht sehr beeindruckend aus.
Benutzeravatar
Krishty
Establishment
Beiträge: 8248
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Effiziente Synchronisation von Threads

Beitrag von Krishty »

Aramis hat geschrieben:Es ist weniger Visual Studio als der Umstand, das die x86 und die AMD64-Architektur kein relevantes Memory-Reordering durchfuehren und volatile somit ausreichend ist, da es immerhin alle compiler-generierten Umordnungen verhindert.
Wo verhindert volatile Umordnung durch Compiler?

  int           thread_2_parameter;
  bool volatile thread_2_may_run;
  …
  void foo() {
    thread_2_parameter = 0;
    thread_2_may_run = true; // PENG
  }


Hier könnte der Compiler die Zuweisung von 0 an thread_2_parameter aufgeschoben haben bis hinter die Zuweiung ans bool volatile. Der 2. Thread würde sehen, dass er laufen darf, bevor sein Parameter gefüllt wurde.

Der Grund, warum Microsoft volatile etabliert haben wollte statt std::atomic war AFAIK, dass Visual C++ einer der wenigen Compiler ist, die volatile-Zuweisungen als Schreibbarrieren verstehen (und volatile-Lesezugriffe als Lesebarrieren) – wo dieser Quelltext also funktioniert. Ich sehe gerade nicht, dass das allen Compilern vorgeschrieben wäre.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten