Parallel Lua / Csp Tutorial (Teil 1)
Vorbereitung
- Es wird die parallele LuaJit VM lvm für das jeweilige eigene Betriebssystem benötigt
Leider kann unter seltenen Bedingungen (Race conditions!) lvm fehlerhaft arbeiten und "abstürzen" → Auch ich als Autor von lvm habe mit den Tücken von parallelen und konkurrierenden Softwaresystem zu kämpfen!
Lua Code Ausführen
- Der Programmcode kann in dem oberen Teilfenster eines Snippets verändert werden.
- Der Programmcode wird ausgfeührt durch Drücken der Playtaste ▸ im unteren Teilfenster (Ausgabekonsole).
- Die Ausgabekonsole kann auf der rechten Seite gelöscht werden durch Drücken von ✗.
- Hin und wieder kann eine Aktualisierung des Ausgabefensters durch Drücken des Knopfes ↻ erforderlich sein.
- Bei parallelen Prozessen werden jeweils neue VM Instanzen in Threads gestartet. Wenn etwas schief läuft kann das Programm hängen und nicht terminieren (also der aktuelle Codeabschnitt). Der Abbruch kann versucht werden durch Drücken des Knopfes ◼.
Eine einfache Textausgabe (1)
▸
◼
|
|
✗
↻
|
Prozesse
Sequenzielle Prozesse
- Sequenzielle Prozesse werden in dem gleichen Thread und der gleichen Coroutine ausgeführt.
- Ein Scheduling ist nicht möglich, aber Blockierung!
Eine einfache Textausgabe (2)
▸
◼
|
|
✗
↻
|
Aufgabe.
- Füge in die beiden obigen seq. Prozesse die sleep(millisec) Funktion vor der Konsolenausgabe print ein (wähle für die verzögerung Werte im Bereich 100-500 ms)
- Wie verhält sich das Programm? Füge die Ausgabe der Prozesszeit ein (
print(time())
) vor und nach dem sleep Aufruf. Alternativ kann anstelle der print die log Anweisung verwendet werden. Diese gibt die aktuelle Prozesszeit in Millisekunden zusammen mit den Argumenten aus.
- Was passiert wenn in einem Prozess ein Fehler auftritt? Also füge z.B. den Aufruf einer nicht definierten Funktion
foo(1)
ein...
Fehlerbehanldung
Die Fehlerbehandlung (genau genommen die Behandlung von Excceptions und Signalen) erfolgt in einem sequenziellen Programm mit der try .. catch (e) .. end
Anweisung (deterministisch)
Die Fehlerbehandlung in parallelen Systemen ist nicht ohne weiteres möglich!
Alle Prozesskonstruktoren werden dann mit einem Fehlersignal "EPROC" abgebrochen:
Fehlerbehandlung
▸
◼
|
|
✗
↻
|
- Häufig ist bei der Fehlersuche der genaue Ort und eine Funktionsaufrufspur (trace) erforderlich. Diese Ausgabe kann durch die Option
debug=true
erreicht werden:
Fehlerbehandlung
▸
◼
|
|
✗
↻
|
Parallele Prozesse
- Die verwendung des Par Konstruktors erzeugt echte parallele Prozesse.
- Parallele Prozesse können nur über definierte IPC Objekete wie Channels kommunizieren
- Variablen können von parallelen Proezsse "geteilt" werden, aber nur als Kopie
- Gemeinsam geteilte Objekte, Variablen und Funktionen müssen nach der Prozessfunktionsliste als Tabelle übergeben werden
Eine einfache Textausgabe mit Prozesskommunikation (2)
▸
◼
|
|
✗
↻
|
Kommunikationskanäle
Ein Channel wird zwischen mindestens zwei Prozessen verwendet um synchronisiert Daten auszutauschen
Ein Channel wird mit dem Konstruktor Channel(n) erzeugt. Die Puffergröße n gibt die maximale Anzahl zwischengespeicherter Daten an.
Ein Channel mit n=0 kann nur von zwei Prozessen (einem Leser und einem Schreiber) verwendet werden → Rendezvous Protokoll
Es gibt zwei Operationen: channel:write(data) und channel:read()
Aufgabe.
- Was läuft bei dem nächsten Beispiel falsch?
- Hinweis: Wenn der Par Prozess nicht terminiert, benutze den ◼ Knopf bis die Meldung mit dem Signal EPROC erscheint!
- Ändere das Programm so ab dass sich beide Prozesse richtig synchronisieren und der Par prozess terminiert.
Eine komplexere Textausgabe mit Prozesskommunikation (3)
▸
◼
|
|
✗
↻
|
Semaphoren
Das wichtigste Kommunikationsobjekt für Prozesse ist die Semaphore als ein geschützer Zähler
Eine Semaphore wird für Produzenten-Konsumenten Systemen und für die Koordination eingesetzt
Eine Semaphore wird mit dem Konstruktor Semaphore(init) erzeugt. Der Startwert gibt den initialen Zählerwert an → die richtige Wahl ist relevant und bestimmt die Anwendung!
Es gibt zwei Operationen: semaphore:up() und semaphore:down()
- Die Downoperation kann den Aufrufer blockieren wenn die Semaphoreninvariante verletzt wird
Semaphoreinvariante (darf zu keinem Zeitpunkt verletzt werden):
\[
sem_{counter} = init + \sum sem:up - \sum sem:down \ge 0
\]
Ein einfaches Produzenten-Konsumenten System
▸
◼
|
|
✗
↻
|
Frage. Wie ist der zeitliche Ablauf?
Frage. Welchen Wert haben die Semaphorenzähler von prod und cons am Ende?
Aufgabe
Dinierende Philosophen
- Dinierende Philosophen ist ein paralleles System welches Semaphoren benutzt um physische Ressourcen abzubilden.
- Es gibt N Philosophen (N ungradzahlig)
- Jeder Philosoph kann sich in zwei Zuständen befinden: "Essend" oder "denkend"!
- Um in den Zustand "Essend" zugelangen benötigt der Philosoph i zwei Gabeln Si und Si+1.
- Jede Gabel wird durch eine binäre Semaphore repräsentiert
- Die Philosophen und Gabeln stellen einen Ring dar, d.h. der letzte Philosopg N benötigt die letzte und erste Gabel.
- Das DP Problem dient als klassisches Deadlock Problem und das Auftreten von Race Conditions (Wettrennen zwischen Threads)
Zwei dinierende Philosopgen - der Auftakt!
▸
◼
|
|
✗
↻
|
- Die Barriere dient hier nur für einen synchronisierten Start der Philosophenprozesse
Aufgabe.
- Starte eine Reihe (10) von Durchläufen. Ist die Ablaufreihenfolge deterministisch?
- Führe in jedem Phil.prozess eine Zählschleife ein die den obigen Anweisungblock kapselt (for)
- Experimentiere mit den auskommentierten random dely (sleep in Millisekunden) und der Threadwechsel mit yield
- Führe den Prozess aus, mit z.b. M=20 Durchläufen. Was lässt sich beobachten?
- Erweitere nun das parallele Prozesssystem auf N=5 Phils. Lässt sich ein Deadlock feststellen? Die Versuche müssen ggfs. vielfach wiederholt werden.
Created by the
NoteBook Compiler Ver. 1.3.9 (c) Dr. Stefan Bosse (Fri Jul 31 2020 09:52:50 GMT+0200 (CET))