Multi-Threading Grundlagen
Motivation
- Reale Prozesse abbilden (Druckjobs, Server-Client)
- Perfromance-Steigerung
-
Seit 2003 stagnieren Taktraten, stattdessen wird mehr auf Hyperthreading und mehrere Kerne gesetzt. Deshalb müssen Programme darauf optimiert werden
-
Hyperthreading (Virtual Threading): Ein CPU-Core hat zwei Register-Sets, aber immer noch nur ein Rechenwerk (ALU)
- Ist nicht "echte" Parallelisierung
Parallelität vs. Nebenläufigkeit
- Parallelität: Zerlegung eines Ablaufs in mehrere Teilabläufe, welche gleichzeitig auf mehreren Prozessoren laufen
- Nebenläufigkeit (Concurrency): Gleichzeitig oder verzahnt ausführbare Abläufe, welche auf gemeinsame Ressourcen zugreifen.
- Älteres Prinzip, z.B. Drucker
- Kann anschein von Parallelität haben, aber es wird immer nur etwas auf einmal ausgeführt
System Grundlagen Repetition
Threads
- Parallele Ablaufsequenz innerhalb eines Programms, teilen den Adressraum mit dem ganzen Prozess
- Ist günstiger als eigener Prozess, braucht keine eigene Pagetable
- Jeder Thread hat eigenen Stack
- User-Level-Thread: Im Prozess implementiert, keine echte Parallelität durch mehrere Prozessoren
- Kernel-Level-Thread: Im Kernel implementiert, kann Multi-Core ausnutzen. Wechsel per SW-Interrupt, ist also nicht ganz "gratis"
- Scheduling: Ein CPU-Core kann immer nur ein Thread gleichzeitig ausführen. Scheduler teilt die Threads zu
- Wenn nur ein Prozessor Threads verarbeitet, ist es eine Quasi-Parallelisierung
-
Programmierung hat für "echte" oder "quasi"-Parallelität die gleichen Probleme, weil man nie weiss, wann die Kontextwechsel geschehen
-
Kontextwechsel
- Synchron: Thread wartet auf Bedingung, gibt den Prozessor selbst frei
- Asynchron: Nach gewisser Zeit wird der Thread gewzungen, die CPU freizugeben
- Multi-Tasking
- Kooperativ: Threads müssen explizit synchrone Kontextwechsel initiieren
- Preemptive: Scheduler unterbricht den Thread asynchron in gewissen Zeitintervallen
- Heute in der Regel immer preemptive!
Grundlagen Java Threads
- JVM ist ein Single Process System
- Main-Thread führt die
main()
-Methode aus (per Reflection) - Programmierer kann weitere Threads starten
- Laufzeitsystem startet auch eigene Threads, z.B. GC
- Die JVM läuft solange, bis kein (nicht-Daemon-) Thread mehr läuft
- Daemon-Thread ist z.B. GC, wird bei Ende der JVM unkontrolliert beendet
class SimpleThread extends Thread { @Override public void run() { // thread behavior // thread ends when run() exits // (or unhandled exception orccurs) } } Thread myThread = new SimpleThread(); myThread.start();
- Wenn unbehandelte Exception in Thread auftritt, laufen andere Threads weiter (in Java, andere Umgebungen anders)
- Threads laufen ohne Vorkehrungen beliebig verzahnt, nicht-deterministischer Ablauf!
Runnable Implementierung statt Vererbung:
class SimpleLogic implements Runnable { @Override public void run() { // thread behavior } } // Pass Runnable to Thread constructor! Thread myThread = new Thread(new SimpleLogic()); myThread.start();
Runnable
-Interface hat 1 Methode -> Functional Interface!- Kann mit Lambdas verwendet werden
Thread myThread = new Thread(() -> { // thread behavior }); myThread.start();
- Mit
Thread.sleep(t[ms])
kann der Thread schlafen gelegt werden Thread.yield()
gibt Thread frei, aber wird gleich wieder ready. Provoziert Thread-Wechsel- Sollte man nicht verwenden, weil OS das Scheduling selbst verwaltet
t.join()
wartet solange, bis der Threadt
terminiert
Thread myThread = new Thread(() -> { // thread behavior }); myThread.start(); // Thread started myThread.join(); // Thread finished
- Threads können von Aussen unterbrochen werden mit
t.interrupt()
- Löst
InterruptedException
aus - Wird meist missbraucht zum Aufbrechen von Blockaden (schlecht!)
- Löst