Das Video kommt von YouTube: erst beim Abspielen verbindet sich die Seite mit YouTube (Google).
Multithreading: Mit Mutex lock und unlock und Signalen arbeiten (C-Programmierkurs)
Das Wichtigste aus dem Video
Tipp auf eine Zeit – das Video springt genau dorthin.
Transkriptautomatisch erstellt · 102 Zeilen
- hallo zusammen in diesem Video schauen wir uns weiter threats an und da vor allem wie wir data races und Race Conditions verhindern können und wie
- threats sich zueinander synchronisieren können ein Link zu dem hier gezeigten qucode findet ihr natürlich wieder in der Videobeschreibung schauen wir uns
- mal ein einfaches Beispiel an in diesem Programm gibt es zwei Funktionen die gleich nacheinander aufgerufen werden count up und Countdown in diesen
- Funktion passiert nicht sehr viel die Funktionen zählen einfach die Variable counter in einer Schleife 1us mal hoch bz Weise runter dabei sollte man
- beachten dass das eine globale Variable ist das heißt dass die beiden Funktionen hier die gleiche variable verändern außerdem legen sich die Funktion hier in
- der Schleife immer für 50 Millisekunden schlafen das soll einfach simulieren dass dort noch andere Dinge gemacht werden wir können uns das einfach
- vorstellen dass der Teil Code eines nachrichtensystems ist und die countup Funktion die Nachrichten empfängt und abspeichert dann den Counter um eins
- erhöht um zu signalisieren dass es eine neue Nachricht gibt und die down Funktion verarbeitet dann diese Nachricht weiter und zählt am Ende
- wieder runter um zu signalisieren dass es keine zu verarbeitende Nachricht mehr gibt in der mainfunktion setzen wir einfach den Counter initial auf Null
- führen dann count up und dann Countdown aus und geben dann aus auf welchem Wert der Counter dann steht wenn wir das Programm jetzt ausführen dann dauert das
- einen kleinen Moment und am Ende sehen wir dann dass der Counter tatsächlich auf Null steht wie erwartet das wurde ja schließlich genauso oft hochgezählt wie
- runter wir können auch einmal einen Timer nutzen um zu messen wie lange die Ausführung dauert tatsächlich dauert das ja recht lang um die Ausführung zu
- beschleunigen können wir uns aber daran erinnern dass wir auch Funktion parallel ausführen können indem wir sie in eigenen Threads starten wie man das
- macht habe ich im letzten Video ausführlich gezeigt den Link dazu findet ihr noch mal in der Videobeschreibung wir legen also wieder zwei Variablen vom
- Typ pthread T an und starten dann die beiden Funktionen mit der Funktion pthread create da müssen wir jetzt keine Argumente übergeben weil die beide
- Funktion ja auf die globale Variable Counter zugreifen können und das ist ja eigentlich die einzige Variable die die beiden Funktionen benötigen um eben zu
- arbeiten danach warten wir noch mit der Funktion pthread join bis beide Funktionen auch tatsächlich komplett ausgeführt wurden und geben danach auch
- wieder den Wert von Counter aus wenn wir jetzt das Programm wieder kompilieren und wieder mit dem Timer starten dann sehen wir dass das Programm auf jeden
- Fall deutlich schneller läuft das ist natürlich super aber wir sehen auch dass das Programm jetzt gar nicht mehr unbedingt das richtige Ergebnis ausgibt
- das Ergebnis sind jetzt völlig zufällige mal positive mal negative Werte aber nicht zuverlässig Null das Problem dabei sind sogenannte data races das passiert
- weil die Befehle counter++ und counter-us Minus keine sogenannten atomaren Befehle sind eigentlich bestehen diese Befehle nämlich aus drei
- einzelnen Befehlen zuerst wird die Variable aus dem Speicher in die CPU geladen dann wird die Variable von der CPU erhöht oder verringert und als als
- letztes wird das Ergebnis davon wieder in den Speicher geschrieben und weil wir jetzt zwei parallel laufende Threads haben zwischen denen jederzeit hin und
- her geschaltet werden kann kann eben auch zwischen diesen Einzelschritten hin und her geschaltet werden es kann jetzt also passieren dass die countervariable
- gerade auf Null steht die countup Funktion die variab liest auf eins verändert aber bevor sie die wieder in den Speicher schreibt könnte jetzt
- Countdown ausgeführt werden und in dieser Funktion kann die Variable auch wieder aus dem Speicher gelesen werden indem sie gerade noch auf Null steht
- dann wird sie dort um eins verringert und wieder in den Speicher geschrieben wenn jetzt die countup Funktion weiter ausgeführt wird dann wird also der dort
- aktuelle Wert in den Speicher geschrieben also steht jetzt die Variable Counter auf 1 obwohl sowohl Countdown als auch countup ausgeführt
- wurden sod dass die Variable eigentlich auf Null stehen müsste das ganze können wir aber natürlich verhindern indem wir den Zugriff auf diese Variable schützen
- s dass immer nur ein Thread gleichzeitig darauf zugreifen kann dazu können wir uns geschützte Bereiche definieren die immer nur von einem Thread zur Zeit
- betreten werden dürfen und das Konzept davon heißt gegenseitiger Ausschluss oder auch mutual exclusion oder kurz Mutex in C gibt es dafür in der pthreads
- Bibliothek auch schon die entsprechenden Funktion und Datentypen um so einen geschützten Bereich zu definieren brauchen wir erst einmal eine sogenannte
- Mutex Variable mit der wir den Bereich identifizieren können dafür gibt es den Datentyp pthread Mutex t ich lege hier so ein eine Variable im globalen
- Speicher an damit dann beide Funktionen auch gleich darauf zugreifen können um diese Variable jetzt benutzen zu können müssen wir diese einmal am Anfang
- unseres Programms initialisieren das können wir mit der Funktion pthread Mutex init machen der wir als ersten Parameter die Adresse einer pthread
- Mutex t Variable übergeben als zweiten Parameter kann man noch weitere Optionen einstellen aber das brauchen wir erstmal nicht und können deshalb Null einsetzen
- um jetzt einen geschützten Bereich zu definieren nutzen wir die Funktion pthread Mutex Log und geben dort die Adresse einer Mutex Variable an wenn wir
- das jetzt in beiden Funktionen direkt vor dem Zugriff auf die countervariable machen verhält sich das Programm folgendermaßen die erste Funktion die
- die Zeile mit dem Mutex Log erreicht sperrt diese Mutex variable und kann weitermachen in der Programmausführung wenn jetzt eine andere Funktion auch
- eine Stelle erreicht an der die gleiche Mutex variable geloggt werden soll dann muss diese Funktion dort warten und kann eben nicht weitermachen aber irgendwann
- soll diese Funktion natürlich auch weiterlaufen und das funktioniert erst wenn die Funktion pthread Mutex Unlock ausgeführt wird also angenommen die
- Countdown Funktion hat zuerst den geschützten Bereich erreicht dann logt sie die Mutex variable verändert die Counter varariable und unlockt dann
- diesen Bereich wieder erst dann kann die countup Funktion den geschützten Bereich betreten wenn wir dieses Programm jetzt kompilieren und ausführen dann ist es
- eigentlich immer noch genauso schnell wie vorher aber zusätzlich bekommen wir auch noch ganz sicher das richtige Ergebnis raus
- kommen wir zum nächsten Punkt und zwar zur Synchronisation zwischen threats stellen wir uns mal eine kleine Abwandlung unseres Programms vor vorhin
- haben wir uns ja vorgestellt dass die countup Funktion simuliert dass Nachrichten in unser System hineinkommen und die Countdown Funktion diese
- Nachrichten verarbeitet jetzt stellen wir uns mal vor dass die Countdown Funktion immer auf fünf neue Nachrichten warten muss bevor sie anfängt diese zu
- bearbeiten wenn fünf Nachrichten da sind dann werden sie bearbeitet und der Counter kann wieder um F verringert werden eine einfache Möglichkeit das
- umzusetzen ist mit einer wild Schleife die eben immer prüft ob Counter noch kleiner als 5 ist wenn das der Fall ist dann passiert hier im Schleifenkörper
- gar nichts aber wir prüfen dann eben danach immer wieder ob diese Variable nicht mittlerweile fünf ist natürlich sollte dieser Zugriff auf diese Variable
- auch wieder durch ein pthread Mutex Lock geschützt werden sonst könnte es sein dass wir prüfen ob die Variable kleiner als fünf ist was in dem Moment wahr sein
- könnte sodass wir dann in den Schleifenkörper gehen in dem Moment könnte die andere Funktion die countervariable aber auf fünf setzen
- sodass wir uns dann in der countdownfunktion innerhalb der Schleife befinden obwohl die countervariable 5 ist was ja eigentlich nicht der Fall
- sein darf diese Situation nennt man dann eine Race condition und die gilt es natürlich auch zu vermeiden nach der Prüfung in der Schleifenbedingung muss
- aber natürlich der Mutex wieder unloggt werden damit die andere Funktion überhaupt die countervariable wieder hochzählen kann am Ende schleifenkörpers
- muss dann aber der Mutex wieder gelockt werden weil als nächstes wieder die Schleifenbedingung geprüft wird wir also wieder auf die countervariable zugreifen
- die Zeile mit dem pthread Mutex Lock genau vor der dekrementierung des Counters brauchen wir dann natürlich nicht mehr da wir wenn wir an dieser
- Stelle angekommen sind ohnehin schon auf jeden Fall in dem geschützten Bereich sind eine Sache die wir noch machen sollten ist dass die vorschleife jetzt
- nur noch ein Fünftel so oft durchlaufen sollte weil wir diese Schleife sonst niemals beenden würden später zeige ich euch noch wie wir das auch mit einer
- Zeitbegrenzung anders lösen können wenn wir das Programm jetzt kompilieren und ausführen dann bekommen wir tatsächlich das richtige Ergebnis
- der Counter steht am Ende wieder auf Null eine Sache ist aber dennoch nicht so gut und das hat mit dieser wildschleife zu tun diese läuft jetzt
- gerade unbegrenzt oft und immer immer wieder durch die Funktion fragt also ständig ist die Variable jetzt noch kleiner als fünf was ist jetzt jetzt
- immer noch und jetzt und jetzt und jetzt also immer wieder fragt sie ob die Variable immer noch kleiner als fünf ist für jede dieser Fragen benötigt der
- Prozessor natürlich Rechenkapazität und das nennt sich dann aktives warten sinnvoller wäre es doch wenn das Programm sich schlafen legen könnte und
- auf ein Signal wartet das ihm einfach signalisiert dass ich die Variable jetzt verändert hat lass uns mal schauen für wie viel Prozessorauslastung dieser
- Programmaufruf jetzt gesorgt hat dazu modifiziere ich diesen timebefehl ein bisschen ich schreibe jetzt also hier command time - F und jetzt in
- Anführungszeichen Zeit Doppel Prozente e das zeigt dir gleich die Zeit an und CPU Proz P zeigt die Prozessorauslastung an und dahinter dann noch den Namen des
- Programms und das sieht schon nach einer recht hohen Auslastung aus um jetzt also die Auslastung zu reduzieren können wir die Funktion pthread Cont weight
- Benutzen dieser übergeben wir eine Adresse einer Variable vom Typ pthread Cont t und eine Adresse zu einem Mutex diese pthread con t Variable ist
- sozusagen ein Signal auf das gewartet wird wenn dies Signal noch nicht gesendet wurde dann hält das Programm hier an und erst wenn so ein Signal
- verschickt wurde dann läuft dieser Thread an dieser Stelle weiter aber warum wird hier eigentlich noch eine Mutex Variable übergeben das hat den
- Grund dass die Funktion pthread Cont weight nicht nur wartet sondern vorher auch noch dieses Mutex unlockt und nach dem Aufwachen auch wieder lockt und zwar
- geschieht das unlocken und Einschlafen als eine atomare Operation sodass zwischen diesen beiden Schritten kein anderer threat in diesen kritischen
- Bereich gelangen kann das gleiche gilt dann auch für das Aufwachen und locken auch das ist zusammen eine atomare Operation wir müssen hier jetzt also das
- explizite Unlock und Log entfernen außerdem müssen wir eine solche pthread Conte variable natürlich erstmal anlegen z.B mit dem Namen Signal das mache ich
- hier auch wieder im globalen Adressbereich weil später die countup Funktion auch auf diese gleiche Variable zugreifen soll damit diese dann das
- Signal senden kann die Variable muss so wie auch die Mutex variable vorhin erstmal initialisiert werden und das mache ich natürlich auch in der
- mainfunktion mit dem Befehl pthread Cont init und dort übergebe ich dann auch die Adresse auf diese condition variable und auch wieder als zweiten Parameter 0 so
- die Countdown Funktion wartet jetzt auf dieses Signal also muss es auch eine Stelle im Code geben an der dieses Signal überhaupt gesendet wird und das
- können wir hier in der countup Funktion machen z.B jedes Mal nachdem der Counter erhöht wurde dazu rufe ich die Funktion pthread Cont Signal auf und übergebe
- natürlich auch wieder die Adresse dieser condition variable wenn wir jetzt das Programm wieder kompilieren und mit dem timebefehl starten dann sehen wir dass
- die benötigte Zeit in etwa gleich ist aber die Prozessorauslastung stark gesunken ist eine letzte Sache die ich euch noch
- zeigen möchte ist wie wir dieses warten mit einem Timer verbinden können es könnte ja sein dass wir gar nicht genau wissen wie oft wir diese Schleife
- eigentlich in der Countdown Funktion ausführen müssen wenn wir diese z.B einfach in eine endlos wildschleife verwandeln denn dann endet unser
- Programm ja eigentlich erstmal nie weil wir keine Abbruchbedingung mehr haben wir können jetzt aber die Funktion pthread Cont weight ersetzen durch
- pthread Cont timed weight diese blockiert nicht für immer sondern bis wie vorher auch ein Signal eintrifft oder bis zu einem bestimmten Zeitpunkt
- diesen Zeitpunkt können wir festlegen indem wir an diese Funktion eine Adresse auf eine Variable vom Typ struct timespack übergeben in diesem Struck
- können Sekunden und Nanosekunden gespeichert werden und dazu muss man sagen die Zeitmessung auf Linux System bezieht sich immer auf die Sekunden die
- seit dem 1 Januar 1970 vergangen sind und wir müssen jetzt angeben zu welchem Zeitpunkt die Funktion pthread Cont timed weight spätestens returnen soll
- dazu können wir uns erstmal die aktuelle Zeit in diesem struct speichern und zwar mit der Funktion clock gettime daübergeben wir clock realtime und die
- Adresse auf dieses timepack struct und wenn wir jetzt möchten dass der Timeout eine Sekunde später zuschlägt dann können wir einfach die Sekunden in
- diesem struct um eins erhöhen also mit ts.tvs das sind die Sekunden da drin + GLE 1 die Adresse dieser Variable übergeben wir jetzt also an pthreadc
- timetimed wait allerdings können wir momentan noch nicht entscheiden warum das Programm an dieser Stelle überhaupt weiter
- ausgeführt werden kann wir wollen aber gerne registrieren wenn das durch einen Timeout zustande kam dazu können wir den Returnwert der Funktion nutzen den
- können wir uns in einer Variable speichern und dann prüfen also z.B mit int Timeout g= pthread Cont timed weight und so weiter wenn dieser Wert jetzt
- Null ist dann kam ein Signal an wenn dieser Wert aber e timed out ist das ist ein Makro das in rno.h definiert ist dann schlug der
- Timeout zu wenn das der Fall ist dann wollen wir hier unser Programm beenden das mache ich jetzt an dieser Stelle mit pthread exit denn den damit kann man ja
- auch einen Threat beenden um zu verdeutlichen dass ihr tatsächlich einen Timeout zuschlägt werde ich noch nach dem letzten hochzählen ein printbefehl
- einbauen und auch direkt vor dem Beenden des Countdown Threads einfach damit man sieht dass an dieser Stelle eine Sekunde gewartet wird und dann eben dieser
- Timeout zuschlägt wenn ich das Programm jetzt kompiliere und ausführe dann sieht man genau das und damit sind wir jetzt auch tatsächlich schon am Ende dieses
- Videos oder bzw dieser drei Videos zum Thema Multithreading angekommen falls Fragen habt dann lasst es mich doch gerne in den Kommentaren wissen und bis
- dahin viel Spaß beim coden [Musik]
Zum Nachlesen
MutexEin kritischer Abschnitt (engl. critical section oder critical region) ist derjenige Teil im ausführbaren Code, in dem ein wegen des Mutex ungestörter …
SpinlockEs ist eine Sperre (Lock) zum Schutz einer gemeinsam genutzten Ressource durch konkurrierende Prozesse bzw. Threads (siehe Kritischer Abschnitt) nach dem …
Kritischer AbschnittKritische Abschnitte bestehen aus mehreren Einzelanweisungen, deren Zwischenergebnisse inkonsistente Zustände darstellen, auf die die anderen Threads keinen …
WettlaufsituationEine Wettlaufsituation, aus dem Englischen auch Race Condition (deutsch Wettlauf-Bedingung) oder Race Hazard (deutsch Wettlauf-Risiko), mitunter auch …