mutex/multithreading



  • Wenn ich eine Variable vorm schreiben locke, muss ich sie dann auch locken wenn es eine Lesemethode gibt die parallel laufen könnte? Oder reicht ein lock-aufruf um die Variable für andere Threads zu sperren?

    bsp:
    thread 1 -> lock(variable) & schreibe //parallel// thread 2 -> lese Variable



  • Ja.



  • noch eine explizitere Frage zum Thema

    ich benutze boost::thread und bin gerade am probieren wie die mutex-geschichte da funktioniert aber auf nen grünen ast komme ich da nicht.

    Habe mir die Theorie zu mutex/critical sections etc durchgelesen aber ich finde in boost nur mutex. Google hat mir dann ein link ausgespuckt der sagte das boost::mutex nicht mit normalen mutexen vergleichbar wär. Aber wie funktioniert das dann?

    a)Was wär was vergleichbares zu einer Critical section in boost

    b) wie lock ich in boost explizit eine Variable, also wenn die möglichkeit besteht das selbige noch in einem anderen Thread, in einem anderen Zusammenhang gelesen/manipuliert wird.
    Ich werde in der boost API da leider nicht fündig :S



  • a) Also eine Critical Section musst du ja durch einen Mechanismus schützen. Das kannst du dann mit einem Mutex, wie ihn boost hat machen.

    b) Indem du vorher ein Mutex lockst, die Variable bearbeitest und dann das Mutex wieder unlockst.

    Wenn du mal die Defintion in boost für Windows anschaust, dann wirst du merken, dass sie da eigentlich das gleiche machen, wie hier als Beispiel gezeigt wird:
    http://en.wikipedia.org/wiki/Critical_section



  • @knivil: "ja" auf eine entweder-oder frage ist fesch 🙂

    @Jud4s:

    Wenn ich eine Variable vorm schreiben locke, muss ich sie dann auch locken wenn es eine Lesemethode gibt die parallel laufen könnte?

    Du musst auch beim Lesen die Mutex locken.

    Habe mir die Theorie zu mutex/critical sections etc durchgelesen aber ich finde in boost nur mutex. Google hat mir dann ein link ausgespuckt der sagte das boost::mutex nicht mit normalen mutexen vergleichbar wär. Aber wie funktioniert das dann?

    Eine Mutex ist erstmal nur ein Konzept. Und natürlich gibt es in Windows sowie auch in PTHREADS jeweils ein konkretes Dinge was sich "Mutex" nennt. Beide entsprechen dem Konzept Mutex, wobei beide nicht ganz gleich sind.
    Windows-Mutexen sind z.B. prozessübergreifend, PTHREADS Mutexen sind das nicht (zumindest per Default - ich glaube man kann das konfigurieren).

    Und die Windows CRITICAL_SECTION setzt auch das Konzept Mutex um.

    Die verschiedenen Boost-Mutex Klassen setzen auch alle das allgemeine Konzept Mutex um, nur mit unterschiedlichen Einschränkungen.

    Eine boost::mutex ist z.B. nicht prozessübergreifend, und kann auch nicht rekursiv gelockt werden.

    a)Was wär was vergleichbares zu einer Critical section in boost

    boost::recursive_mutex

    CRITICAL_SECTION & boost::recursive_mutex...
    * sind nicht prozessübergreifend
    * können rekursiv gelockt werden
    * haben eine "try-lock" funktion
    * haben keine "timed-lock" funktion
    * sind relativ schnell

    In älteren Boost Versionen war boost::recursive_mutex (auf Windows) auch über CRITICAL_SECTION implementiert. Neuere Boost-Versionen kochen ihr eigenes Süppchen.

    b) wie lock ich in boost explizit eine Variable, also wenn die möglichkeit besteht das selbige noch in einem anderen Thread, in einem anderen Zusammenhang gelesen/manipuliert wird.

    boost::recursive_mutex g_m;
    int g_i = 123;
    
    void foo()
    {
        boost::unique_lock<boost::recursive_mutex> lock(g_m); // g_m wird hier gelockt
        g_i++;
    } // g_m wird hier wieder freigegeben
    

    p.S.: man kann keine "Variablen locken". Man kann nur Mutexen locken. Wenn man sich daran hält, immer die selbe Mutex zu locken, bevor man auf eine bestimmte Variable zugreift, dann kann man dadurch natürlich den Zugriff auf diese Variable synchronisieren. Die Variable selbst interessiert das aber wenig, und wenn sich ein anderer Code-Teil nicht daran hält, und einfach auf die Variable zugreift (ohne irgendwas zu locken), dann funktioniert das schöne Spiel nichtmehr, und es können Fehler passieren.



  • Wenn man sich daran hält, immer die selbe Mutex zu locken

    den satz kapier ich jetzt nicht.
    Ich habe die Theorie so verstanden das man eine globale Variable lockt wenn sie geschrieben wird und shared_locked wenn sie nur gelesen wird.
    Critical Sections sind dafür gut das wenn verschiedene Threads den selben Codeabschnitt verwenden sich nicht in die quere kommen, wie z.b. zwei threads die gleichzeitig die gleiche Methode benutzen um ein attribut eines Objektes auszulesen und dann um eins zu erhöhen.

    Nur eure Ausführungen passen jetzt irgendwie nichtmehr ganz in mein Verständnis. Gibt es da ein Theorie-Tutorial damit ich das Konzept nochmal mit anderen Quellen lernen kann?

    €: deletet a whole lotta stuff



  • Vergiss Reader-Writer-Locks ("shared_lock"), das ist was für Profis und sogar die bauen gerne Mist damit.
    Verwende einfach immer "unique" Locks, egal ob gelesen oder geschrieben wird.

    Was CRITICAL_SECTION angeht: der Name ist total irreführend. Eine CRITICAL_SECTION ist einfach eine Mutex.

    Code-Abschnitte muss man auch nicht irgendwie schützen - eine Funktion kann gut und gerne von mehreren Threads gleichzeitig ausgeführt werden.
    Schützen muss man den Zugriff auf (nicht-lokale) Variablen bzw. Daten im Allgemeinen.
    Und dafür verwendet man Mutexen - ob nun CRITICAL_SECTION oder Windows MUTEX oder boost::mutex ist erstmal egal.



  • ok das war wiederrum etwas nebelklärender 🙂

    aber was richtig nett wär wenn man an meiner Code Idee diese Mutexe benutz und kurz einen Kommentar anfügt wieso das so klappt bzw die Logik.

    Das würde mir das Verständnis unendlich erleichtern.



  • Hi

    Also ich beschäftige mich auch gerade damit. Is seit ein paar stunden neu das Thema für mich. Von daher bitte gleich mitteilen, wenn ich total daneben bin!!!.
    Habe ich das richtig verstanden:
    (Beispiel von Hustbaer)

    Wenn irgendwo ein Schreibprozess ist, welcher:
    boost::recursive_mutex g_m;
    nicht lockt, bevor er auf
    int g_i
    zugreift, funktioniert die synchronisation nicht mehr ??.

    Kurtz, es kann gut sein, dass ein zweiter Prozess in den momentan aktiven kommt
    während das Mutex g_m gelockt ist, aber nur dann, wenn er (der unterbrechende Prozess) selber g_m nicht lockt ???



  • boost::recursive_mutex g_m;
    int g_i = 123;
    
    void foo()
    {
        boost::unique_lock<boost::recursive_mutex> lock(g_m); // g_m wird hier gelockt
        g_i++;
    } // g_m wird hier wieder freigegeben
    

    wie kann g_m hier g_i beeinflussen? Mit welcher Logik erkennt der Compiler das?
    Und was meinst du mit "immer den gleichen mutex locken"?



  • Jud4s schrieb:

    wie kann g_m hier g_i beeinflussen? Mit welcher Logik erkennt der Compiler das?
    Und was meinst du mit "immer den gleichen mutex locken"?

    Der Compiler erkennt da gar nix! Zur Laufzeit wird geschaut -> g_m locked? no: exec; yes: wait.
    DU bist dafür verantwortlich, dass immer dann wenn g_i verwendet wird, der mutex gelocked ist, und zwar g_m! Wenn du meinst "gut, muss ich locken, mach ich mal neuen mutex g_nm", dann hast du Pech, da niemand deinen mutex verwendet sondern g_m, und dann trotz mutexen gleichzeitiger Zugriff stattfindet!



  • so das jetzt nochmal so das ich es verstehe^^

    woher weiß der mutex das er auf g_i aufpassen soll?

    Wie soll das funktionieren? Die Logik dahinter?



  • Jud4s schrieb:

    woher weiß der mutex das er auf g_i aufpassen soll?

    Der Mutex weiß gar nicht, worauf er aufpassen soll! Du musst das wissen, und entsprechend einen (DEN EINEN!) Mutex locken und bitte auch wieder unlocken...



  • ahhh kapiert 😃 danke



  • wär es da nicht eigentlich sinnvoll eine klasse zu erstellen die einer Variable einen mutex zuordnet und diesen Speichert und das jedesmal wenn man eine methode von der Klasse z.b. lock("variablenname) aufruft wird geguckt ob es dafür schon ein mutex gibt der dann gelockt wird oder ggf wird ein neuer erstellt.
    Oder gibt es sowas schon bzw ist das nicht sinnvoll?



  • AlexanderKiebler schrieb:

    Wenn irgendwo ein Schreibprozess ist, welcher:
    boost::recursive_mutex g_m;
    nicht lockt, bevor er auf
    int g_i
    zugreift, funktioniert die synchronisation nicht mehr ??.

    korrekt

    Kurtz, es kann gut sein, dass ein zweiter Prozess in den momentan aktiven kommt
    während das Mutex g_m gelockt ist, aber nur dann, wenn er (der unterbrechende Prozess) selber g_m nicht lockt ???

    Ich verstehe den Satz nicht ganz, bzw. ich denke du verwendest den Begriff "Prozess" da nicht ganz richtig.

    Aber egal.
    Ein Mutex ist einfach ein "Ding", das immer nur einer (ein Thread) "haben" kann.
    Mit "lock" kann man sich dieses Ding nehmen. Hat es schon ein anderer, muss man warten bis dieser es wieder hergibt. Kannst du dir vorstellen wie ne Karte die auf nem bestimmten Tisch liegt wenn sie "frei" ist, bzw. wenn nicht dann hat sie grad irgendwer in der Hand.
    Anders gesagt: es ist sichergestellt, dass immer nur ein Thread gleichzeitig eine Mutex "gelockt" haben kann.

    WENN man sich als Programmierer nun daran hält, dass man immer nur an einer bestimmten Datenstruktur rummacht, während man auch "die zugehörige" Mutex "hält", dann ist damit sichergestellt, dass diese Datenstruktur nie von zwei Threads gleichzeitig bearbeitet wird.

    Irgendwelche Automatismen oder Hilfestellungen des Betriebssystems gibt's allerdings keine, d.h. man muss selbst sicherstellen dass dieser "Vertrag" eingehalten wird.



  • Jud4s schrieb:

    wär es da nicht eigentlich sinnvoll eine klasse zu erstellen die einer Variable einen mutex zuordnet und diesen Speichert und das jedesmal wenn man eine methode von der Klasse z.b. lock("variablenname) aufruft wird geguckt ob es dafür schon ein mutex gibt der dann gelockt wird oder ggf wird ein neuer erstellt.
    Oder gibt es sowas schon bzw ist das nicht sinnvoll?

    Es gibt ähnliche Ansätze, hat sich aber nicht durchgesetzt.
    Der Grund ist denke ich, dass solche Hilfsmittel in Wirklichkeit wohl wenig hilfreich wären. Oftmals (fast immer) schützt man mehrere Variablen über die selbe Mutex, und will auch auf mehrere Variablen gleichzeitig zugreifen nachdem man sich die Mutex geholt hat. Jedesmal in die "Lock" Anweisung 3, 4, 5 Variablennamen mit reinzuschreiben wäre äusserst lästig, und auch einigermassen Fehleranfällig.

    Und wenn man den Fall hat, dass man gleichzeitig zwei oder mehr Mutexen locken muss, dann muss man verdammt aufpassen, dass man es immer in der selben Reihenfolge macht, sonst hat man schnell einen Deadlock. Und es gibt nur wenige Dinge die lästiger zu Debuggen sind als ein - am besten noch schwer reproduzierbarer - Deadlock.

    Und zu guter Letzt: man kann den Code der mit Mutexen rumhantieren muss oft schön in eigene Klassen wegkapseln, so dass der grossteil des Programms sich nicht darum kümmern muss. Und in den Klassen die dann mit Mutexen rumhantieren müssen, ist die Sache oft so klar, dass man keinerlei Hilfe benötigt.
    Eine synchronisierte Queue-Klasse z.B. braucht nur eine Einzige Mutex. Alle Zugriffe auf Membervariablen werden über diese Mutex gesteuert, d.h. im Endeffekt holt sich jede Memberfunktion zuerst die Mutex, macht dann was zu tun ist, und gibt sie dann wieder frei.



  • ok danke, bin auch gerade zum Schluss gekommen das mir das zu kompliziert zu programmieren ist für dieses kleine Problem was ich habe 😛



  • Hi hustbaer und Jud4s ,
    Also was mich interessieren würde ist Folgendes:

    boost::recursive_mutex g_m;
    int g_i = 123;
    int main1(void)
    {
    void foo()
    {
        boost::unique_lock<boost::recursive_mutex> lock(g_m); // g_m wird hier gelockt
        g_i++;
    } // g_m wird hier wieder freigegeben
    }
    
    boost::recursive_mutex g_m;
    int g_i = 123;
    int main2(void)
    {
    void foo()
    {
        boost::unique_lock<boost::recursive_mutex> lock(g_m); // g_m wird hier gelockt
        g_i++;
    } // g_m wird hier wieder freigegeben
    }
    

    Also zwei Programme, parallel ausgeführt. Die Variable g_i soll z.B. shared Memory sein. Die Frage:
    Ist das Mutex g_m aus der ersten main-funktion (Prozess) gleich dem g_m aus der zweiten ???
    Oder gibt es jetzt zwei mutexe mit dem Namen g_m, so wie es eigentlich normal ist. Wenn das der Fall ist, wie mache ich so ein Mutex für beide bekannt ??
    In shares Memory darf ich ja keine Objekte erstellen soweit ich das verstanden habe......

    Kann ich Folgendes Sagen:
    -Ein Mutex hat zwei Zustände, offen und geschlossen.
    -Ein offenes Mutex kann man schließen.
    -Ein Schließen eines geschlossenen Mutex blockiert bis es wieder geöffnet wird.
    -Nur der Prozess wo das Mutex geschlossen hat, darf es auch wieder öffnen.

    Gruß



  • hustbaer schrieb:

    @knivil: "ja" auf eine entweder-oder frage ist fesch 🙂

    Jud4s schrieb:

    Wenn ich eine Variable vorm schreiben locke, muss ich sie dann auch locken wenn es eine Lesemethode gibt die parallel laufen könnte?


Anmelden zum Antworten