09.4 Synchronisation von Threads, lock
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 54 | |
Author | ||
License | CC Attribution - NonCommercial - ShareAlike 3.0 Germany: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/9697 (DOI) | |
Publisher | ||
Release Date | ||
Language | ||
Producer |
Content Metadata
Subject Area | |
Genre |
Informatik 2, Sommer 201152 / 54
1
2
9
10
11
13
17
37
38
43
45
46
47
50
51
53
54
00:00
Variable (mathematics)Moment (mathematics)NumberZugriffWordSound effectZahlComputer scienceMicroprocessorCalculationService (economics)Thread (computing)Software developerSynchronizationComputer animationDiagram
06:11
NumberVariable (mathematics)Computer animation
07:43
Variable (mathematics)NumberHausdorff spaceThread (computing)Interior (topology)CarriagewayComputer animationDiagram
10:24
Computer animation
10:51
Thread (computing)NumberRandom number generationStatement (computer science)Object (grammar)SynchronizationComputer scienceZugriffFlock (web browser)Computer animation
11:59
Thread (computing)ZugriffSynchronizationInformatikstudiumComputer animationDiagram
12:37
Variable (mathematics)Object (grammar)Thread (computing)Social classZugriffSakokuWritingMicroprocessorSummationQuery languageComputer fileCompilerSynchronizationNumerisches GitterHypermediaComputer animation
18:50
KommunikationThread (computing)Computer animation
Transcript: German(auto-generated)
00:00
Ich hatte versprochen, dass ich noch was Besseres zeige als Volatile. Volatile ist die rustikale Methode für Mikroprozessuren. In den modernen Sprachen nimmt man typischerweise etwas anderes. In Java schreibt man Synchronize und in C-Sharp schreibt man Lock. Das will ich einmal vorführen, wie das aussieht.
00:32
Ich habe zwei Threads, Thread 1 und Thread 2. Diese beiden Threads teilen sich eine Variable, die ich eben mit den Variablen zum Starten und Stoppen gemacht habe.
00:50
Diese Variable würde ich Volatile machen, wenn ich es nicht anders geregelt kriege mit komplizierteren oder moderneren Geschichten.
01:02
Der Job der Thread soll sein, diese Variable um 1 zu erhöhen. Jeder Thread zählt, wie viele Arbeitsschritte er gerade erledigt hat, wie viele Kilowattstunden er gerade benachrichtigt würde. Jeder Thread soll zählen, gemeinsam in dieser Variable, dass dann nachher in der Variable der gesamte Zählerstand steht.
01:28
Nicht ein Zählerstand pro Thread, sondern ein gemeinsamer Zählerstand. Wenn alles mit rechten Dingen zugeht, überlegt sich vielleicht irgendwann der erste Thread, dass es jetzt so weit wäre diese Zahl zu erhöhen.
01:48
Er liest die 13, er liest die 13, addiert 1 drauf, kriegt 14 raus, schreibt das zurück und dann stellt vielleicht der Thread 2 irgendwann fest,
02:03
oh jetzt ist aber der Zähler jetzt weiter zu stellen, er liest die 14, addiert 1 drauf, kriegt 15 raus und schreibt die 15 zurück. Das wäre der Idealfall, wenn das so passiert, ist die Welt in Ordnung und dann kriegt man das schön auch mit dem Wolle-Teil hin.
02:22
Der Ärger ist, es kann schief gehen, jedes so und so hundertste, zehntausendste Mal wird das schief gehen. Das ist auch wieder eine ganz blöde Fehlerquelle, das ist überhaupt ein ganz dringendes Thema bei dem Multi-Threading. Sie können sich grandiose Fehler einbauen, die aber erst drei Jahre
02:43
später auftreten, weil irgendein Kunde dann plötzlich mit einem 16 Kernrechner ankommt und sie es vorher nie auf so einer Maschine gesehen haben. Man muss sehr vorsichtig sein beim Programmieren. Das ist auch ein dickes Thema in der Informatik, es überlegen ganz viele Leute, wie man das denn sicher hinkriegen kann, das Multi-Threading,
03:01
wie man solche Fehler grundsätzlich vermeiden kann. Dieses Parallel-Fall ist zum Beispiel eine ganz einfache Idee, solche Fehler grundsätzlicherweise schon ein bisschen einzuschränken. Hilft noch nicht total, aber hilft schon ein bisschen. Das wäre die Wunschsituation, jeder fährt, zählt eins hoch, zum Schluss steht da 15, zwei mehr.
03:23
Jetzt kann aber sein, dass die beiden sich gleichzeitig überlegen, dass die Variable erhöht werden muss und dann passiert Blödsinn. Also nächstes Schaubild für die Nummer 8. Alle glauben dasselbe zu tun und plötzlich passiert Blödsinn. Ich habe den Thread 1, ich habe den Thread 2 und ich habe eine gemeinsame genutzte Variable.
03:49
Gemeinsame Variable. Und die steht zu Beginn von mir auf 13. Der erste Thread sagt sich irgendwann, ich muss jetzt mal um eins weiter schalten, schreibt seine 14 zurück.
04:06
Das kann ihnen passieren, dass der zweite Thread, nicht häufig, aber irgendwann wird es passieren, dass der zweite Thread im gleichen Moment sagt, ich muss ja mal bitte von der 13 umschalten, eins weiter, auf die 14. Und das wäre natürlich Blödsinn, sie hätten es nicht wirklich gezeichnet.
04:24
Ich soll das im zeitlichen Ablauf noch mal klar machen. Da bleibt immer noch die 13 drin stehen und jetzt sagt der zweite Thread, während der erste hier gerade addiert, das geht zwar schnell, aber es geht nicht in unendlich kurzer Zeit.
04:40
Während der erste addiert, sagt der zweite, ich muss mal weiter stellen und schreibt dann hier nochmal eine 14 rein. So ist es vielleicht klarer von der Struktur her. Das heißt, wenn die beiden sich nicht abstimmen beim Zugriff auf diese Variable, habe ich irgendwann einen Zähler vergessen und zum Schluss vielleicht hunderte oder tausende Zähler vergessen durch diesen Effekt.
05:04
Und ich weiß vorher auch gar nicht, wie viel ich vergessen haben werde. Das hängt von der Maschine ab, von den äußeren Umständen, wie viel jetzt zwischendurch passiert. Wenn der drei Stunden rechnet und dann einmal zwischendurch eins weiterzählt, ist das Thema nicht so gravierend, als wenn der ständig weiterzählt.
05:22
Wenn jeder von mir aus 100 Nanosekunden, 10 Nanosekunden weiterzählt, dann ist das hier ein viel dickeres Problem. Also je nach äußeren Umständen passiert das hier häufiger, seltener oder gar nicht. Auf jeden Fall gefährlich. Das darf auch nicht theoretisch passieren. Man muss sicherstellen, dass dieses nicht mal theoretisch passieren darf.
05:43
Wenn Sie nur mit Volatile arbeiten, wird das irgendwann passieren. Also wenn es nur darum geht, eine Variable auszulesen, eine Variable setzen. Okay, aber sobald es jetzt allein das schon ist, eine Variable um eins erhöhen, laufe ich Gefahr, dass etwas schief geht. Ich brauche eine Synchronisierung zwischen den Threads.
06:03
Die müssen sich abstimmen beim Zugriff auf die Variable. Das zeige ich nun mal, wie das aussehen kann. Ich brauche hier erstmal zwei Threads, die irgendwelche Arbeiten machen. Dazu, wie üblich, meine Methode, die die Arbeit erledigt.
06:21
Do the work. Wenn die gruppieren können, egal. Sie können ja Gänsefüße machen. Wie üblich bis zu 100, 10 Millionen. 10 Millionen hatte ich ausnahmsweise. 10 Millionen, 1, 2, 3, 1, 2, 3.
06:47
Ich brauche diese Variable. Das ist alles für Kontext Nummer 9. Ich brauche diese Variable, die hochgezählt wird, natürlich. Und ich mache die auch ganz ordentlich Volatile. Nur das hilft uns hier gar nicht. Es hilft uns etwas, aber das macht es noch nicht korrekt.
07:07
Nehmen wir sie VolatileInt, wie auch immer. Zähler. So nennen wir sie. Und jetzt zähle ich einfach diesen Zähler hier jeweils. Eins rauf.
07:20
Sie sehen, das sieht so aus wie ein Befehl, ist aber nicht ein Befehl. Plus plus heißt ja den alten Wert lesen. Und dann um eins erhöhen. Und dann wieder zurückschreiben. Das ist nicht, streng genommen, ein Befehl. Das ist nicht atomar, wie man so schön sagt. Das ist nicht atomar, sondern das wird nachher in einzelnen Schritten rausgeführt. Das ist nicht ein einziger Schritt.
07:42
Deshalb gibt es ja gerade diesen Ärger, dass einer dazwischen funken kann. So, und wenn ich fertig bin, möchte ich einfach ausgeben, hier was dann im Zähler steht. Soll ich das so machen? Ja, mach das einfach hier. In System Diagnostics.
08:01
Jeder Thread, der hier zählt, gibt aus, weißte denn, als letzten Wert gesehen hat in Zähler. System Diagnostics. Trace. Right Line. Zähler. So, das ist die Methode. Diese Methode soll jetzt von zwei Threads parallel ausgeführt werden.
08:22
Beide machen denselben Job. Hier mit derselben Variable Zähler. Keine Angst, dieses Int i haben sie nicht gemeinsam. Die lokalen Variablen hat jeder Thread für sich. Also jeder Thread hat dann sein eigenes i. Diese Methode wird ja aufgerufen. Und damit wird automatisch eine neue Kopie hier für diese Variable i angelegt.
08:41
Also das hat jeder Thread alleine, was hier in lokalen Variablen haben. Der Zähler ist aber gemeinsam. Beide erhöhen denselben Zähler gleich. Beide Threads, die ich jetzt noch bauen muss, die gibt es ja bisher nicht. Also was wir eben hatten. System Threading. Ups. System Threading. Thread.
09:02
Mein erster Thread soll ein neuer Thread sein. Sehr überraschend. Und Do Work machen. Do Work. Do Work soll er machen. Damit man hoffentlich mehr sieht, stelle ich ihn mal wieder auf Low.
09:22
Priority. Lowest. Und starte ihn. Start. Jetzt baue ich noch einen zweiten. Mit Copy und Paste, der genau dasselbe macht. Das heißt diese Do Work Methode läuft an zwei Stellen gleichzeitig.
09:44
Zwei Arbeiter machen dasselbe. Aber das Wesentliche ist, dass sie einfach mitzählen. Wie häufig sie was machen. Die zählen hier mit. Zum Schluss sollte hier. Beide werden, was war das jetzt?
10:02
Zehn Millionen Mal. Beide werden zehn Millionen Mal ausgeführt. Jeder Thread läuft zehn Millionen Mal. Das heißt, in Zähler sollte zum Schluss 20 Millionen drin stehen. Bei jedem Mal erhöhen wird um eins erhöht. In Zähler sollten 20 Millionen stehen. Auf dieser Maschine habe ich es noch nicht ausprobiert. Bei mir zu Hause stehen nicht 20 Millionen drin. Ich bin mal gespannt, wie es auf dieser Maschine kommt.
10:29
Okay, in dieser Maschine stehen 17 Millionen drin. Ich hoffe, Ihnen wird das Problem dann an der Stelle etwas klarer. Das sieht total harmlos aus. Man kann es jetzt noch mal machen.
10:41
Halt. Ach so, er zählt ja weiter. Er zählt ja weiter. Das sind jetzt aber auch wahrscheinlich nichts. Das doppelt. Ich sollte es vielleicht einmal auf Null setzen. Dann kann man die Werte besser vergleichen. Ich setze mal zu Beginn auf Null hier. Wie hieß der Zähler? Ich setze den Zähler mal auf Null, bevor ich das neu mache.
11:02
Okay, also der erste Thread hat irgendwie aufgehört bei 15 Millionen. Dann war der fertig. Und dann müsste der nächste Jahr noch bis 20 Millionen weiter zählen. Aber wir sehen, er endet bei 15.800.000. Irgendwas. Das sehen wir noch mal. Der erste endet bei 13 Millionen. Der andere muss noch mal weiter zählen. Er endet bei 17 Millionen.
11:20
Das ist praktisch Zufall, wo sie landen. Je nachdem, wie diese Befehle kommen, verhaken sie sich nicht oder verhaken sich. Das wäre ein genialer Zufallszahlengenerator hier. Na ja, vielleicht etwas zu genial. Aber das können Sie als Zufallszahlengenerator verwenden. Es kommen nicht 20 Millionen raus.
11:42
Wenn das ordentlich funktionieren würde, 2 Threads zählen von 0 bis 9.999.000. Dann müsste es zum Schluss auf 20 Millionen stehen. Steht es aber nicht. Und das heißt, es ist weniger. Und das heißt, es passiert genau das, was ich hier beschrieben habe.
12:02
Das passiert. Man kann nicht genau vorhersagen, wann es passiert, wie häufig es passiert. Es wird irgendwann passieren. Hier ist es ganz besonders drastisch in diesem Beispiel. Als Warnhinweis. Vorsicht. Wenn sich mehrere Threads miteinander unterhalten, muss man dafür sorgen, dass diese Zugriffe irgendwie koordiniert werden.
12:20
Synchronisation. Kann man im echten Informatikstudium wahrscheinlich zwei Semester was darüber erzählen. Ich wollte Ihnen das hier nur bringen als Problem, dass es Ihnen bewusst ist. Was ist eigentlich das Problem bei Threads? Neue Threads zu bauen ist nicht das Problem. Das Problem ist, Threads miteinander reden zu lassen. Und das scheint so wohl nicht zu funktionieren.
12:44
Die Lösung. Wie mache ich das jetzt hier mal? Schreibe ich das alles hintereinander? Ja. Ich schreibe die Nummer 10 einfach mal dreiß dazwischen. In Java ist die Lösung mit einem Schlüsselwort, das heißt synchronise. In C sharp heißt es lock.
13:03
Abschließen. Ich benutze ein zusätzliches Objekt, um diese Zugriffe zu synchronisieren. Das ist in beiden Sprachen in die Idee. Ich brauche ein weiteres Objekt für den Nummer 10. Das kann irgendwas sein. Manchmal bietet es sich an, irgendeines der Objekte zu nehmen, die man schon hat.
13:24
Eine Liste, die man eingerichtet hat. Ein bestimmter Gegenstand, der da sowieso in der Gegend rumfliegt. Ich richte jetzt einfach etwas Neues ein. Dieses Objekt dient nur dazu, sich zu merken,
13:41
ob gerade jemand etwas kritisches tut mit meiner Variablen. So, in der Form. Irgendein Objekt, was ich benutzen kann. Und dieses Objekt, jetzt heißt es Dummy. Objekt ist ja das grundlegende Objekt von allen, die grundlegende Klasse von allen.
14:03
Diesen Dummy bitte ich nun quasi sich zu merken, ob jemand anders schon was Problematisches tut. Und wenn das der Fall ist, muss ich warten. Das kommt hier rum. Ich schreibe Lock Dummy. Das wäre für den Nummer 10.
14:21
Darum herum. So lange, wie ich auf diese gemeinsame Variabel zugreife, setze ich das alles in dieses Lock rein. Vielleicht jetzt sage ich mal mehr, was das bedeuten soll. Das selbe mache ich hier in dem anderen Thread.
14:42
An der Stelle. An der Stelle bringt es das nicht wirklich, aber jetzt nur ganz konsequent. An der Stelle läuft ja keiner von den anderen Threads, aber ich schreibe es da trotzdem rein. In der Ordnung halber. So sind jetzt alle Zugriffe auf diese gemeinsame Variabel eingebettet in dieses Lock.
15:04
Was nun passiert ist folgendes. Wenn ein Thread hier ankommt, guckt er, ob sich dieses Dummy Objekt gemerkt hat, dass da schon jemand reingegangen ist. Ob jemand das Schloss aufgemacht hat. Wenn schon jemand reingegangen ist in so einen Bereich, muss jeder, der danach kommt, warten.
15:22
Der bleibt hier bei dem Lock stehen, bis der, der da drin ist, wieder rausgegangen ist. Das ist eigentlich wie ein Telefonhäuschen, wie ich mir darüber nachdenke. Das ist wie ein Telefonhäuschen. Der Lock hier, das bildet ein Telefonhäuschen. Es kann nur eine Person zu einer Zeit drin sein. Dieses Dummy wird so ein Telefonhäuschen.
15:40
Wenn hier einer drinnen ist, müssen alle anderen draußen warten. Wenn der erste Thread hier drinnen ist, muss der zweite draußen warten. Wenn der erste Thread rausgeht, kann der erste hier reingehen und drin arbeiten. Das heißt, dieses Plus-Plus wird höchstens von einem Thread zu einer Zeit ausgeführt.
16:00
Insgesamt. Da kann kein zweiter Thread rein funken. Das ist die übliche Art der Synchronisation. Sie brauchen ein Hilfsobjekt, das eigentlich nur dazu da ist, um sich zu merken, ob jemand da drin ist in so einem Bereich. Und alle diese Bereiche, die jetzt mit Dummy hier gelockt sind, in allen diesen Bereichen zusammen darf immer nur ein einziger Thread stehen.
16:23
Oder keine. Es kann sein, dass gerade keiner an den Stellen ist. Wenn einer hier oben drin ist, weil das mit derselben Variabel hier geht, weiß der Dummy, oh, es ist einer drin, dann darf auch hier unten keiner rein. Und alle müssen vor dem Lock warten. Und erst wenn der hier oben rausgeht, dürfen die auch hier unten wieder rein.
16:42
Und die können es natürlich mehr machen als nur Plus-Plus. Hier können sie zwischendurch auch ein Datei aufmachen, eine Datenbank abfragen. Einfach nur um sicherzustellen, dass dieser ganze Bereich, Critical Section heißt das dann manchmal auf Englisch, ein kritischer Abschnitt, Critical Section, dass dieser ganze Bereich geschützt ist, insofern, als das nur ein Thread zu einer Zeit da drin arbeiten darf.
17:03
Und mit diesem Lock geht es ja sogar noch weiter. Es ist nicht nur dieser Bereich geschützt, sondern alles, was den selben Dummy hier hat, ist insgesamt geschützt. Da darf nur ein Thread zu einer Zeit drin stehen. Jetzt kann ich hier das Voloteil auch noch loswerden. Ich habe Ihnen gesagt, Voloteil ist antik. Das braucht man dann nicht mehr mit dem Lock.
17:25
Sorgt der Compiler dafür, dass das auch funktioniert. Sie brauchen das Voloteil nicht mehr davor zu schreiben. Dieses Lock sagt dem Compiler nebenbei, dass er bestimmte Sachen nicht umsortieren darf, dass er bestimmte Sachen aus dem Speicher holen muss, in den Speicher schreiben muss.
17:41
Es funktioniert von selbst richtig. Ich will es gar nicht im Detail erklären, da kann man Stunden mitverwenden. Wenn Sie das Lock drum haben, funktioniert es von selbst. Sie brauchen das Voloteil nicht mehr. Das Voloteil ist, wie gesagt, eher was für die klassischen Mikroprozessoren. So, das waren wenige Änderungen. Ich habe hier einfach um alle Zugriffe das Lock geschrieben.
18:00
Da war Synchronize. Und ich habe dieses Objekt Dummy eingerichtet, dass ich merke, ob gerade einer drin ist. Und ich bin das Voloteil sogar noch losgeworden. Jetzt müssten wir hoffentlich 20 Millionen rauskriegen.
18:22
20 Millionen. So muss das sein. Und ich hoffe auch wiederholbar. Wiederholbar 20 Millionen. Sie sehen nebenbei, der erste Thread endet früher als der zweite. Der erste Thread zieht nur 15 Millionen. Und dann zählt der nächste Thread noch bis zu seinem Ende. Deshalb ist die Anzahl bei dem ersten Thread immer so verschieden.
18:42
Wichtig ist, dass die Summe von beiden stimmt. Jeder Thread muss 10 Millionen mal laufen. In der Summe müssen es 20 Millionen sein. So funktioniert das dann. Das ist die billigste Art, wie man diese Situation lösen kann. Das kann man endlos weitertreiben. Ich wollte Ihnen zumindest die wichtigste Art einmal gezeigt haben. Und das Problem gezeigt haben.
19:03
Kommunikation zwischen Threads ist das Schwierigste von allem an der Stelle.