Thread - sichern eines kritischen Bereichs



  • SeppJ schrieb:

    Vielleicht gibt es zwei Arrays, die immer abwechselnd gelesen und beschreiben werden, mit nur einem einzigen atomic, der anzeigt, welcher Thread gerade welches Array nehmen soll. Letzteres wäre mein Favorit.

    Wollte ich zuerst auch machen, aber das geht so direkt nicht. Denn niemand kann wechseln. Der Schreiberthread nicht, denn wenn er ganz schnell ist, könnte er beide überschreiben, während der andere immer noch am lesen ist. Der Leser hätte das gleiche Problem.

    Ich habe das mit einem Busy-wait gelöst, denn es sollte ja eher selten auftreten.



  • SeppJ schrieb:

    Kommt drauf an 🙂 .

    Von sich aus bringen die Datenstrukturen der Standardbibliothek keine Locks mit. Weil sie nicht müssen und es würde in den allermeisten Fällen stören. Du kannst dir in Referenzen und im Standard ansehen, welche Sicherheiten garantiert sind, aber das sind genau die, die man erwarten würde. Faustregeln:
    -Gleichzeitiges Lesen ist ok, egal wo.
    -Gleichzeitiges Lesen und Schreiben ist ok, wenn an jeweils unterschiedlichen Stellen und wenn das Schreiben keine Umstrukturierung des Containers verursachen kann.
    -Gleichzeitiges Lesen und Schreiben ist nicht ok, wenn das Schreiben eine Umstrukturierung des Containers verursachen kann.
    -Gleichzeitiges Lesen und Schreiben an der gleichen Stelle ist nicht ok.

    Falls die letzten beiden Fälle auftreten können, musst du das selber absichern.

    Habe bestimmt irgendwelche Sonderfälle vergessen. vector<bool> ist beispielsweise bestimmt wieder anders, aber das kannst du selber nachgucken.

    Für mich als blutjungen Anfänger, der nie irgendwas parallelisiert hat, zum Verständnis:

    Heisst das, dass ich mit Thread1 an einem deque vorne pushen und mit Thread2 hinten poppen (:D) könnte, ohne dass die sich gegenseitig auf die Eier gehen?

    Und was passiert wenn der eine Thread den anderen aufholt, konkret, wenn der hinten schneller poppt als vorne gepusht wird?



  • Skym0sh0 schrieb:

    Heisst das, dass ich mit Thread1 an einem deque vorne pushen und mit Thread2 hinten poppen (:D) könnte, ohne dass die sich gegenseitig auf die Eier gehen?

    Wäre denkbar, dass das threadsafe ist, aber garantiert wird da nix (vielleicht macht pop_back ein assert(size()!=0)).

    Und was passiert wenn der eine Thread den anderen aufholt, konkret, wenn der hinten schneller poppt als vorne gepusht wird?

    Dann hat man sowieso ein Problem.

    Suche mal nach thread-safe Queue (gibts sogar lock-free), das ist genau das, was du beschreibst.


  • Mod

    Skym0sh0 schrieb:

    Heisst das, dass ich mit Thread1 an einem deque vorne pushen und mit Thread2 hinten poppen (:D) könnte, ohne dass die sich gegenseitig auf die Eier gehen?

    Nein. push_front und pop_back sind beides modifizierende Zugriffe. Also die Verwaltungsdaten des Containers selbst modifizierend. Daher darfst du keine weiteren Operationen dieser Art gleichzeitig ausführen. Du darfst aber zumindest bei push_front noch gleichzeitig über Pointer und Referenzen auf andere Elemente zugreifen, nicht aber über Iteratoren. Bei pop_back gilt ähnliches, aber du darfst natürlich nicht auf das letzte Element zugreifen.



  • cl90 schrieb:

    die zu schreibende lesenden var, ist ein D3DXVECTOR3.

    Also können auf einer MehrkernCPU auch tatsächlich aufrufe gleichzeitig geschehen?

    Ja.
    Was hättest du denn gedacht dass die einzelnen Cores machen wenn nicht gleichzeitig "nebeneinander" zu rechnen?
    Genau dazu baut man ja mehrere Cores in die CPU rein. Weil man halt in der gleichen Zeit mehr getan bekommt wenn 4 Arbeiter gleichzeitig arbeiten. Vorausgesetzt sie kommen sich nicht in die Quere, und da wären wir dann auch schon beim Thema Synchronisierung 🕶

    Wenn ja muss ich es sichern, denn bei ungünstigem timing liest Thread 1 während 2 gerade eine der variablen ändert. Dann hätte ich entweder einen gemischten vector aus alten und neuen Daten oder sogar bruchteile von Informationen.

    Dazu braucht es keine Mehrkern-CPU. Dazu braucht es noch nichtmal etwas wie Hyper-Threading. Kann dir mit nem 386er genau so passieren.
    Bei ungünstigem Timing wird Thread 1 vom OS unterbrochen * nachdem er 2 der 3 Komponenten geschrieben hat, und die CPU wird an Thread 2 übergeben. Dieser liest dann alle drei Komponenten ohne unterbrochen zu werden, und hat auch Mischwerte bekommen.

    Am besten ist es aber sich auf dieser Ebene erst gar keine Gedanken zu machen. Weil es nämlich noch andere Effekte gibt die da mit reinspielen, auf die immer gerne vergessen wird. Bzw. noch wichtiger: wenn man protablen Code schreiben will, dann darf man nie-nie-nie danach gehen was mit irgendeiner konkreten Implementierung wirklich passieren könnte, sondern darf sich nur danach richten was der Standard des "Systems" mit dem man arbeitet garantiert (in diesem Fall ist das "System" C++ und der Standard daher der C++ Standard).

    ok.. also für eine ganz einfach Sicherung. Was bietet sich da an?
    Es handelt sich um 3 Float Werte, dh die Zugriffszeit eines Threads wird kurz sein. Der Andere Thread darf auch nicht lange warten, denn es handelt sich um eine 3D Simulation, die beim Lesen des Vektors nicht stehen bleiben sollte. (FPS Einbruch)

    Der schnellste "klassische" Locking-Mechsnismus für Fälle wo der Lock nur für extrem kurze Zeiten gehalten wird, ist ein Spin-Lock.
    Bieten aber die wenigsten Threading-Libs an.
    Die zweitschnellste Variante sollte dann eine lokale, nicht-rekursive Mutex sein.

    Wobei Mutex hier nicht mit dem zu verwechseln ist was Windows eine "Mutex" nennt (=> WinAPI Funktion "CreateMutex").
    Das was Windows unter "Mutex" versteht ist nämlich eine nicht-lokale rekursive Mutex, und speziell unter Windows um mehrere Grössenordnungen langsamer als die Windows-Variante der lokalen Mutex aka. CRITICAL_SECTION.

    *: Das nennt man preemptives Multitasking. Windows konnte das z.B. ab Windows 95. Dabei wird über einen Timer ein Interrupt ausgelöst der den laufenden Thread unterbricht. Der Interrupt-Handler ist Teil des OS. Darin wird der laufende Thread dann schlafen gelegt, und ein anderer Thread wird zum neuen laufenden Thread.
    Der unterbrochene Thread kann weder wissen wann er unterbrochen wird noch dies irgendwie beeinflussen (**). D.h. er kann auch mitten während der Ausführung des Codes unterbrochen werden der für ein einzelnes C oder C++ Statement generiert wurde. Sogar wenn dieses Statement nur eine Zuweisung eines einzelnen "int" ist.

    **: Unter Windows 95 konnte man es noch relativ leicht beeinflussen, unter späteren Windows-Versionen aber nicht mehr. Und zum guten Ton hat es noch nie gehört.



  • cl90 schrieb:

    Also: Thread 2 liest zyklisch einen UDP strom. Sobald er seine Bytes zusammen hat, schreibt er die Daten in einen Vector und beginnt wieder zu lesen.
    Thread 1 greift auch zyklisch auf den Vektor zu. (einmal pro Durchlauf lesen).
    Oder kommt es dabei überhaupt nicht zu Problemen?

    Wie oft pro Sekunde passiert das jeweils (worst case)?
    Ich kann mir nämlich grad nicht vorstellen dass die Verwendung einer Mutex dich da auch nur irgendwie spürbar ausbremsen wird.
    Selbst wenn du die (vergleichsweise) superlangsame (nicht-lokale, rekursive) Windows "Mutex" nimmst.



  • hustbaer schrieb:

    cl90 schrieb:

    Also: Thread 2 liest zyklisch einen UDP strom. Sobald er seine Bytes zusammen hat, schreibt er die Daten in einen Vector und beginnt wieder zu lesen.
    Thread 1 greift auch zyklisch auf den Vektor zu. (einmal pro Durchlauf lesen).
    Oder kommt es dabei überhaupt nicht zu Problemen?

    Wie oft pro Sekunde passiert das jeweils (worst case)?
    Ich kann mir nämlich grad nicht vorstellen dass die Verwendung einer Mutex dich da auch nur irgendwie spürbar ausbremsen wird.
    Selbst wenn du die (vergleichsweise) superlangsame (nicht-lokale, rekursive) Windows "Mutex" nimmst.

    gewünschte aktualisierungsrate von Thread1: 60x pro Sekunde. Thread 2 darf ein bisschen dadrüber liegen, soll aber relativ ähnlich laufen. Bzw mal sehen wie schnell sein bytebuffer voll wird.
    Denn Thread2 wird maßgeblich durch den Bytestrom gebremst. Ich könnte zwar beim lesen des Sockets einen Timeout setzen, aber ich will ja ganze Daten haben. Also warte ich bis das Päckchen komplett ist. Und ich bin mir fast sicher das ich nicht 60 UDP Päckchen vom Smartphone gesendet bekomme in so kurzer zeit.
    Also vermutlich wird Thread2 bei 20-30 ticks pro Sekunde laufen.



  • 60x pro Sekunde ist genau gar nichts.
    Da könntest du Brieftauben schicken und wärst immer noch schnell genug.

    => Nimm die erstbeste Mutex Klasse und verwende diese.

    (Dass du den Lock nur halten solltest während du den Vektor updatest bzw. liest, und nicht während du renderst oder auf neue Daten über's Netzwerk wartest sollte klar sein.)



  • jop das war klar. gelockt nur für lese schreibe zugriff.

    Dh. Ihr habt die ganze Zeit an Echtzeitbereich gedacht? Ab wann bremst Mutex denn merkbar?



  • CRITICAL_SECTION Lock + Unlock braucht so 50~500 CPU Zyklen (je nach CPU, ohne Lock-Contention).
    Ein paar hunderttausend Lock + Unlock pro Sekunde sind also sicher kein Problem.


Anmelden zum Antworten