Synchronisationsproblem mit Threads: Denkfehler?



  • Ja, da hast du wohl wahr, wenn ichs mir recht überlege.
    Aber das setEvent/ResetEvent ist ja auch nur ne Krücke, weil es mit Pulse Event überhaupt nicht ging. Wenn ich das recht verstanden habe, sollte doch bei PulseEvent mit einem manuell resettenden Event alle auf den Event wartenden Threads weitermachen und direkt danach die Events resettet werden oder? Läuft aber nicht.
    Was deinen 🤡 angeht, hat er wohl auch wieder recht. Bei Threads hab ich manchmal echt ein Brett vor dem Kopf 😮
    Acuh auf die Gefahr hin, das da wieder was besonders bescheuertes bei rumkommt probier ich das mal:

    //Signal the threads (strings/body) to compute the next step
    SetEvent(m_syncEvent);
    ResetEvent(m_syncEvent);
    //Wait for threads to signal they have finished
    WaitForMultipleObjects(kNumStrings+1,&m_readyHandles[0],true,INFINITE);
    

    Ist das logischer? Wahrscheinlich...
    Vielen Dank
    Sören


  • Mod

    Nein! Das hindert auch keinen Thread daran zweimal die Runde zu drehen.

    Füge einen neuen Event ein auf den alle Threads warten nachdem Sie die arbeit getan haben.
    Du solltest den Event setzen umd die Arbeit anzustossen.
    Dann warten bis alle fertig sind. Die Threads laufen in den Warte Event rein.
    Dann den Event zurüclkstetzen. Warte Event auslösen. und nun warten alle Threads wieder brav. Anschließend den Event zurücksetzen, auf den die Threads nach der Arbeit warten.



  • Ja, im Prinzip hast du recht.
    Was jetzt aber witzigerweise passiert ist folgendes:
    Die Thread-Schleifen brauchen so lange, um von SetEvent zum unteren WaitForSingleObject zu kommen, dass in der Zeit das Event gesetzt und zurückgesetzt wurde. Wie kann ich denn sicherstellen, dass er auch wirklich wartet und nicht nur in der Zeile vorher ein Event rausgibt?

    void PMBodyThread::Execute(void *arg)
    {
        while(true)
        {
        WaitForSingleObject(m_syncHandle,INFINITE);
        ((PMBodyInterface*)arg)->computeSound();
        SetEvent(m_readyHandle);
        WaitForSingleObject(m_sync2Handle,INFINITE);
        }
    }
    

    Kann mir jemand sagen, wie das mit PulseEvent funktioniert? Oder automatisch resettenden Events? Die sollten doch eigentlich dafür gemacht sein, oder? Hab aber das Gefühl das sie nicht funktionieren....



  • Also, ich werd wahnsinnig.
    Ich hab ihm jetzt noch eine weitere Sicherheitsstufe geschenkt. Das ganze sieht so aus:

    while(true)
    	{
    		SetEvent(m_ready2Handle);
    		WaitForSingleObject(m_syncHandle,INFINITE);
    		ResetEvent(m_ready2Handle);
    		((PMString*)arg)->getOutput();
    		SetEvent(m_readyHandle);
    		WaitForSingleObject(m_sync2Handle,INFINITE);
    		ResetEvent(m_readyHandle);
    	}
    

    Was jetzt passiert? Die Threads bleiben bei der vorletzten Reihe hängen, weil der Hauptthread nicht auf die SetEvents reagiert und auf die wartet. Falls jemand noch irgendeine andere Lösung für das gewurschtel hat?!???
    Ich brauch ne Pause
    Schönen Abend!
    Sören



  • ich könnt jetzt versuchen mich an meine paralleldatenverabeitungsvorlesungen zu erinnern ....aber das artet wieder in kopfschmerz aus .... darf man fragen wie du dir die abarbeitung der threads genau vorstellst ? diese parallelausführung und zusammenführung der threads garantiert dir zwar das alle threads ihre berechnung gleichzeitig abschliessen aber du verschwendest natürlich rechenzeit während die schnellen auf die langsamen threads warten ...

    aber an deiner stelle würd ich die threadsteuerung mehr zentralisieren und weniger die threads sich gegenseitig freigeben lassen

    wie gesagt ich kann mir leider nicht vorstellen was genau du berechnest


  • Mod

    Also wenn Du immer auf alle Threads wartest (und da lohnen sich höchstens zwei) Frage ich mich wirklich ob hier irgendwas schneller berechnet wird. Bei dem Aufwand, den Du hier treibst.

    IMHO benötigst Du nur 4 Events,
    - StartEvent (signalisiert den Threads den Start),
    - ReadyForWorkEvent für jeden Thread, der sagt ich warte auf Arbeit.
    - DoneEvent signalisert aus dem Hauptthread, dass die Arbeit getan ist.
    - NextJobEvent signalisiert den Threads wieder in die Startphase zu gehen.

    Alle Events sind zurück gesetzt.
    1. Alle Threads setzen Ihr ReadyForWorkEvent und warten auf StartEvent
    2. Haupthread wartet das alle Threads ReadyForWork sind.
    3. Hauptthread reseted NextJobEvent (könnte bei mehreren Dürchläufen gesetzt sein (siehe unten)
    4. Haupt Thread setzt StartEvent
    5. Hauptthread wartet auf alle DoneEvents
    6. Worker setzen alle irgendwann DoneEvent und warten nun auf NextJobEvent Event.
    7. Sind alle DoneEvents vorhanden, resetted der Hauptthread das StartEvent und setzt NextJobEvent Event.
    8. Alle Worker laufen wieder los und es geht bei 1. weiter. Mit dem Unterschied, dass aktuell noch NextJobEvent gesetzt ist. Das klärt sich bei 3

    Wichtig ist hier, dass die signalsierenden Events nicht zurückgesetzt werden bis alle Threads wieder einen definierten Status ereicht haben.

    Das ganze kann auch leichter gehen... 🕶



  • Ja vielen Dank!
    So hab ich mir das bei meinem letzten Posting gedacht, und nachdem ich einen Schreibfehler korrigiert hatte, lief es auch (ich sollte meine Events besser benennen...) Leider nicht schnell genug, wobei ich aber noch austesten muss, ob das ganze Thread gewurschtel einen zu großen Overhead mit sich bringt, oder die ganze Sache allgemein zuviel Leistung verschlingt.

    Was ich mir im Prinzip gedacht hatte war folgendes: Es soll eine Gitarre mit Physical Modeling berechnet werden. Da bis auf die (mechanischen) Schnittstellen, Saiten und Korpus getrennt voneinander berechnet werden können, soll jeder Berechnung ein Thread zugeordnet werden, der bei Bedarf von Windows auf meine BEIDEN GELIEBTEN PROZESSORKERNE verteilt werden kann. Nun müssen aber natürlich Saiten und Korpus synchron miteinander agieren. Nützt ja nichts, wenn der Korpus schon 10mal rechnet mit dem selben Eingangswert, der von der Saite kommt. Eigentlich könnte man das auch in einem einzigen Thread machen und ich berechne einfach zuerst die Saiten und dann den Korpus. das hätte nur den Nachteil, dass man das nicht auf zwei Kerne verteilen kann.
    Ist das jetzt wenigstens logisch durchdacht, oder bin ich mal wieder auf dem Holzweg?
    Viele Grüße und danke für alles
    Sören
    P.S. Habt ihr denn einen Tipp wie das leichter funktioniert?


  • Mod

    Dann verstehe ich Deinen Ansatz nicht. Natürlich darfst Du dann die Berechnung nur einmal ansetzen.

    Es lohnen sich auch nur so viele Threads wie Du auch Kerne hast, also aktuell 2-4, je nach Hardware.

    Zerlege Deine Berechnungen in Objekte und lass diese eben auch nur dann berechnen wenn es nötig ist. Das Verfahren was wir hier verwendet haben eignet sich eher Paralellberechnung für ein und den selben Rechnvorgang.

    Vorgänge die bereits berechnet sind, darfst Du eben nicht neu berechnen.
    Wie sollen wir Dir hier helfen?



  • Also, mal abgesehen davon, dass der ganze Thread-Wust mehr bremst als nützt ist die Sache doch klar oder? Um Missverständnisse zu vermeiden:
    Alles was ich will, ist, zwei unahängige Berechnungen, die pro Zeitsample einmal durchgeführt werden müssen, auf zwei Threads und somit zwei Kerne zu verteilen. Alles, was darüber hinaus passiert ist, ist wohl eher meiner programmiertechnischen Unkenntnis als bewusstem Handeln zuzuschreiben. Kann sein, dass ich euch damit verwirrt habe?
    Die Berechnungen sind ja in Objekten angelegt, alles ganz easy. Im Single-Thread sieht das so aus:

    m_theBody->computeSound()
    m_theString->computeSound()
    

    Hab es nur nicht anders geschafft zu sagen: Thread1 bitte einmal, Thread 2 bitte einmal, und nächstes Sample!
    Vielleicht hätte ich einfach die Frage stellen sollen, wie ich diesen Code auf zwei Kerne verteilen kann? Tut mir leid, dass ich das nicht eher klar gemacht habe...
    Grüße
    Sören


  • Mod

    - Dann erzeuge eine Liste mit Objekten die berechnet werden sollen. Abgesichert durch eine Critical Section.
    - Erzeuge soviele Threads wie Kerne vorhanden sind.
    - Jeder Thread holt sich ein Element aus der Liste (abgesichert durch eine Critical Section) und fürht die Berechnung durch.
    - Wenn Die Liste leer ist. terminieren die Threads, oder warten einfach bis wieder was in der Liste ist.



  • Ok, das hört sich gut an.
    Würde das dann etwa so aussehen?:

    while(true)
        {
           EnterCriticalSection(&cs);
           object=list->pop();
           LeaveCriticalSection(&cs);
           object->computSound();
        }
    

    Wie warte ich auf ein Objekt in der Liste?
    Jetzt muss der HaupThread aber immer noch auf das Ergebnis der Berechnungen warten.
    Das sieht mir wieder nach ein paar Events aus?
    Vielen Dank für deine Mühe!
    Grüße
    Sören


  • Mod

    Durch ein Event! Derjendige, der etwas in die Liste packt, setzt das Event. Der Thread, der das letzte Element rausholt. Setzt den Event zurück!
    So schwer?

    Das Objekt, dass Du von der Liste Popst kann doch einen Event enthalten. Auf diesen Event muss natürlich der nutzende Thread warten...



  • Jaja, irgendwie hätte ich das schon hinbekommen, aber das sieht mir dann nicht unbedingt schneller aus als die Version, die wir schon haben, oder?
    Am laufen hab ichs ja, es bremst nur mehr als es nützt.
    Grüße
    Sören


  • Mod

    Wenn die ARbeitsabschnitte zu klein sind, hast Du mehr Overhead als es eben Effekt bringt.
    Das ganze bringt es eben nur wen die Threads sehr unterschiedliche Dinge tun und eben auch warten oder eben auch Kalkulationen durchführen, die lange genug dauern.

    Wenn Du jedesmal neue Threads startest ist der Overhead immens...



  • Ne, neue Threads starten kommt nicht in Frage, klar. Aber die Arbeitsschritte sollten zumindest bei der Saite relativ groß sein. Da habe ich ne Schleife mit 60 durchgängen und einigen Additionen und Multiplikationen. Der Korpus ist von der Last her etwas unregelmäßig (was leider nicht zu ändern ist), mal schauen.
    Ok, ich glaube, wir können den Thread 😃 abschließen.
    Vielen Dank für deine Anregungen und Hilfe!
    Sören


Anmelden zum Antworten