Zum Inhalt springen
L

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)

Frozen Code14:16 592 Aufrufe veröffentlicht Auf YouTube

Das Wichtigste aus dem Video

Tipp auf eine Zeit – das Video springt genau dorthin.

Transkriptautomatisch erstellt · 102 Zeilen
Herunterladen
  1. 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
  2. threats sich zueinander synchronisieren können ein Link zu dem hier gezeigten qucode findet ihr natürlich wieder in der Videobeschreibung schauen wir uns
  3. mal ein einfaches Beispiel an in diesem Programm gibt es zwei Funktionen die gleich nacheinander aufgerufen werden count up und Countdown in diesen
  4. 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
  5. 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
  6. 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
  7. vorstellen dass der Teil Code eines nachrichtensystems ist und die countup Funktion die Nachrichten empfängt und abspeichert dann den Counter um eins
  8. 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
  9. wieder runter um zu signalisieren dass es keine zu verarbeitende Nachricht mehr gibt in der mainfunktion setzen wir einfach den Counter initial auf Null
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. arbeiten danach warten wir noch mit der Funktion pthread join bis beide Funktionen auch tatsächlich komplett ausgeführt wurden und geben danach auch
  18. 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
  19. 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
  20. 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
  21. weil die Befehle counter++ und counter-us Minus keine sogenannten atomaren Befehle sind eigentlich bestehen diese Befehle nämlich aus drei
  22. 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
  23. letztes wird das Ergebnis davon wieder in den Speicher geschrieben und weil wir jetzt zwei parallel laufende Threads haben zwischen denen jederzeit hin und
  24. her geschaltet werden kann kann eben auch zwischen diesen Einzelschritten hin und her geschaltet werden es kann jetzt also passieren dass die countervariable
  25. 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
  26. 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
  27. 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
  28. aktuelle Wert in den Speicher geschrieben also steht jetzt die Variable Counter auf 1 obwohl sowohl Countdown als auch countup ausgeführt
  29. 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
  30. 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
  31. 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
  32. Bibliothek auch schon die entsprechenden Funktion und Datentypen um so einen geschützten Bereich zu definieren brauchen wir erst einmal eine sogenannte
  33. 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
  34. 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
  35. unseres Programms initialisieren das können wir mit der Funktion pthread Mutex init machen der wir als ersten Parameter die Adresse einer pthread
  36. 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
  37. 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
  38. das jetzt in beiden Funktionen direkt vor dem Zugriff auf die countervariable machen verhält sich das Programm folgendermaßen die erste Funktion die
  39. die Zeile mit dem Mutex Log erreicht sperrt diese Mutex variable und kann weitermachen in der Programmausführung wenn jetzt eine andere Funktion auch
  40. 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
  41. soll diese Funktion natürlich auch weiterlaufen und das funktioniert erst wenn die Funktion pthread Mutex Unlock ausgeführt wird also angenommen die
  42. Countdown Funktion hat zuerst den geschützten Bereich erreicht dann logt sie die Mutex variable verändert die Counter varariable und unlockt dann
  43. 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
  44. eigentlich immer noch genauso schnell wie vorher aber zusätzlich bekommen wir auch noch ganz sicher das richtige Ergebnis raus
  45. kommen wir zum nächsten Punkt und zwar zur Synchronisation zwischen threats stellen wir uns mal eine kleine Abwandlung unseres Programms vor vorhin
  46. haben wir uns ja vorgestellt dass die countup Funktion simuliert dass Nachrichten in unser System hineinkommen und die Countdown Funktion diese
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. sodass wir uns dann in der countdownfunktion innerhalb der Schleife befinden obwohl die countervariable 5 ist was ja eigentlich nicht der Fall
  54. 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
  55. aber natürlich der Mutex wieder unloggt werden damit die andere Funktion überhaupt die countervariable wieder hochzählen kann am Ende schleifenkörpers
  56. 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
  57. 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
  58. 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
  59. 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
  60. Zeitbegrenzung anders lösen können wenn wir das Programm jetzt kompilieren und ausführen dann bekommen wir tatsächlich das richtige Ergebnis
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. Programmaufruf jetzt gesorgt hat dazu modifiziere ich diesen timebefehl ein bisschen ich schreibe jetzt also hier command time - F und jetzt in
  67. 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
  68. 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
  69. 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
  70. 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
  71. verschickt wurde dann läuft dieser Thread an dieser Stelle weiter aber warum wird hier eigentlich noch eine Mutex Variable übergeben das hat den
  72. 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
  73. geschieht das unlocken und Einschlafen als eine atomare Operation sodass zwischen diesen beiden Schritten kein anderer threat in diesen kritischen
  74. 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
  75. 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
  76. hier auch wieder im globalen Adressbereich weil später die countup Funktion auch auf diese gleiche Variable zugreifen soll damit diese dann das
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. die benötigte Zeit in etwa gleich ist aber die Prozessorauslastung stark gesunken ist eine letzte Sache die ich euch noch
  83. 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
  84. eigentlich in der Countdown Funktion ausführen müssen wenn wir diese z.B einfach in eine endlos wildschleife verwandeln denn dann endet unser
  85. Programm ja eigentlich erstmal nie weil wir keine Abbruchbedingung mehr haben wir können jetzt aber die Funktion pthread Cont weight ersetzen durch
  86. pthread Cont timed weight diese blockiert nicht für immer sondern bis wie vorher auch ein Signal eintrifft oder bis zu einem bestimmten Zeitpunkt
  87. diesen Zeitpunkt können wir festlegen indem wir an diese Funktion eine Adresse auf eine Variable vom Typ struct timespack übergeben in diesem Struck
  88. können Sekunden und Nanosekunden gespeichert werden und dazu muss man sagen die Zeitmessung auf Linux System bezieht sich immer auf die Sekunden die
  89. 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
  90. 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
  91. 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
  92. 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
  93. timetimed wait allerdings können wir momentan noch nicht entscheiden warum das Programm an dieser Stelle überhaupt weiter
  94. 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
  95. 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
  96. 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
  97. 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
  98. 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
  99. 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
  100. 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
  101. 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
  102. dahin viel Spaß beim coden [Musik]

Zum Nachlesen