Effiziente Synchronisation von Threads
Effiziente Synchronisation von Threads
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
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
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
- Schrompf
- Moderator
- Beiträge: 5045
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
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.
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.
Re: Effiziente Synchronisation von Threads
Ok, danke, das mit dem Yield nehme ich mir nochmal vor.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.
SuspendThread und ResumeThread vielleicht?
Was :oIch werde meine Zeit nicht für noch eine Diskussion dieser Art verschwenden.
LG
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
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.
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.
Re: Effiziente Synchronisation von Threads
Hi,
Ok das werde ich prüfen, falls es nicht der Befehel ist nehme ich den und prüfe mal die Update-rate.
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
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.
der Code synchronisiert eine Funktion die von mehreren Threads Ausgeführt wird.Ansonsten habe ich mir den Quelltext nicht weiter angeschaut weil ich nicht wirklich begriffen habe, was er tun soll.
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
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
-
- Establishment
- Beiträge: 426
- Registriert: 23.01.2013, 15:55
Re: Effiziente Synchronisation von Threads
Ich glaube nicht das Yield intern diesen Befehl verwendet.
"Pause" heißt, den Kern (kurz) warten lassen; "Yield" heißt, anderen Thread rechnen lassen.void yield();
Provides a hint to the implementation to reschedule the execution of threads
Re: Effiziente Synchronisation von Threads
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): 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: 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 :(
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): 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: 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 :(
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
-
- Establishment
- Beiträge: 426
- Registriert: 23.01.2013, 15:55
Re: Effiziente Synchronisation von Threads
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.
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.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
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.
Re: Effiziente Synchronisation von Threads
Hi,
Für die Synchonisation des Threadpool sind die Wartezeiten wohl manchmal zu lang für _mm_pause().
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^^
Ich werde das mal in meinem Projekt probieren. Da gibt es Stellen wo sehr kurz gelockt werden muss."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.
Für die Synchonisation des Threadpool sind die Wartezeiten wohl manchmal zu lang für _mm_pause().
Mal sehen ob das gelingt.Am Wichtigsten ist es lange von kurzen Pausen zu unterscheiden
ResumeThread() und SuspendThread() scheinen prinzipiell gut geeignet. Leider ist es mit damit noch nicht gelungen.andere Ansätze anschauen wie das Suspend/Resume, Condition Variablen etc.
Nun ich habe von 1 bis 16 durchprobiert und die Updaterate aufgezeichnet.Das sieht aus als ob du zu viele Threads für deinen Zweikerner erzeugst.
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^^
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
Re: Effiziente Synchronisation von Threads
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.
Auch verstehe ich wie Schrompf deinen Code nicht. Nirgendswo wird irgendeine Arbeit gemacht.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
Er meint mit Synchronisierung wohl mehr sowas wie WaitForMultipleObjects(ALL), glaube ich … jedenfalls arbeitet das Konzept im Augenblick so.
Re: Effiziente Synchronisation von Threads
Das ist mir bekannt, jedoch erscheint das Konzept sinnvoll, um mehrere Threads die warten müssen aus dem "busy wait" zu bekommen.ResumeThread() und SuspendThread() sind nicht zur Synchronisierung gedacht und sollten auch nicht dafür verwendet werden.
Ich glaube "CriticalSections" helfen mir nicht bei "warten lassen" von Threads auf einander.
Ich habe nochmal ein vereinfachtes Beispiel von dem Einsatz meines ThreadPool-Entwurfes.Auch verstehe ich wie Schrompf deinen Code nicht. Nirgendswo wird irgendeine Arbeit gemacht.
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
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
Nein, die sind überhaupt nicht für sowas geeignet, steht sogar extra der Hinweis in der MSDN:Horus hat geschrieben:ResumeThread() und SuspendThread() scheinen prinzipiell gut geeignet. Leider ist es mit damit noch nicht gelungen.andere Ansätze anschauen wie das Suspend/Resume, Condition Variablen etc.
;)MSDN hat geschrieben:This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. [...]
Schau dir mal Condition Variablen, Events, I/O Completion Ports etc. an.Horus hat geschrieben:Das ist mir bekannt, jedoch erscheint das Konzept sinnvoll, um mehrere Threads die warten müssen aus dem "busy wait" zu bekommen.ResumeThread() und SuspendThread() sind nicht zur Synchronisierung gedacht und sollten auch nicht dafür verwendet werden.
Ich glaube "CriticalSections" helfen mir nicht bei "warten lassen" von Threads auf einander.
Re: Effiziente Synchronisation von Threads
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.
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.
Re: Effiziente Synchronisation von Threads
Das ist mir im Grunde bekannt. Eine Begründung findet sich nicht so richtig.Nein, die sind überhaupt nicht für sowas geeignet, steht sogar extra der Hinweis in der MSDN
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.
Ok.Schau dir mal Condition Variablen, Events, I/O Completion Ports etc. an.
Jein.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?
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.
Ich habe in der Zielanwendung etwa 1000 Updates/Sekunde. Für jedes Update erwarte ich, dass 3 Mal Threads.Sync(). benötigt wird.Die einfachste Lösung wäre dann tatsächlich WaitForMultipleObjects. Dann musst du die Threads aber für jede Aufgabe neu erstellen.
Neu erstellen von Threads kommt bei einer solchen Frequenz nicht in Frage :(
Wenn ich es so mache, wie schlafen sie?Der Mainthread kann dann über ein zweites Event wieder alle Threads aufwecken.
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.
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
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.
2. Erklärung: The Old New Thing – Why you should never suspend a thread.
Re: Effiziente Synchronisation von Threads
Auf ein Event wartest du mit WaitForSingleObject. Die Funktion habe ich ja auch schon erwähnt.Horus hat geschrieben:Wenn ich es so mache, wie schlafen sie?Der Mainthread kann dann über ein zweites Event wieder alle Threads aufwecken.
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.
Re: Effiziente Synchronisation von Threads
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
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
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
-
- 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
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.
Re: Effiziente Synchronisation von Threads
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:
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.
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
);
...
}
Re: Effiziente Synchronisation von Threads
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
);
...
}
Re: Effiziente Synchronisation von Threads
Danke, das Kompliment kann ich nur zurückgeben.Dass ist nett, dass du ein minimales Beispiel gemacht hast.
Die Lösung ist echt cool.Du brauchst allerdings mindestens zwei Events. Die Racecondition hast du ja schon selber erkannt. Ohne Mainthread ist das etwas tricky, aber es geht so:
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.Performanter kann man so ein Problem nicht lösen.
Die Threads holen sich Arbeitspakete ab, diese sind relativ klein. So bekomme ich das bisher in den Griff.Du musst auch bedenken, dass sobald ein Thread länger braucht als die anderen er alle blockieren wird
Ich arbeite seit längerem an einem "seltsamen" Computerspiel.Das Problem ist aber sehr komisch und mich würde interessieren, wozu du sowas brauchst.
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();
}
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
Re: Effiziente Synchronisation von Threads
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.
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.
-
- Establishment
- Beiträge: 426
- Registriert: 23.01.2013, 15:55
Re: Effiziente Synchronisation von Threads
Dafür gibt es doch Atomics oder insbesondere "std::memory_order". "volatile" finde ich etwas unellegant, weil es das nicht genau aussagt.Du solltest vielleicht auch noch TestRunning1 volatile markieren [...]
Re: Effiziente Synchronisation von Threads
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?
Gibt's zu Win32 Events eigentlich auch ein Pendant in der Standard Bibliothek?
Re: Effiziente Synchronisation von Threads
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...
MFG
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...
OK mache ich.Ja, man sollte wohl wirklich aus dem bool ein std::atomic<bool> machen.
Oh das wüsste ich auch sehr gerne.Gibt's zu Win32 Events eigentlich auch ein Pendant in der Standard Bibliothek?
MFG
Videos von meinem Projekt: https://www.youtube.com/watch?v=AKKoZFE ... eB&index=1
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
Ich hatte vergessen zu sagen, dass das fantastisch aussieht :-)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.
- Aramis
- Moderator
- Beiträge: 1458
- Registriert: 25.02.2009, 19:50
- Echter Name: Alexander Gessler
- Wohnort: 2016
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
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.Ja, man sollte wohl wirklich aus dem bool ein std::atomic<bool> machen. Aber solange man Visual Studio verwendet reicht auch volatile.
Dem schliesse ich mich an. Sieht sehr beeindruckend aus.Ich hatte vergessen zu sagen, dass das fantastisch aussieht :-)
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Effiziente Synchronisation von Threads
Wo verhindert volatile Umordnung durch Compiler?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.
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.