Paradigmen der Programmierung Nebenläufigkeit

Slides:



Advertisements
Ähnliche Präsentationen
Be.as WEB Technologie
Advertisements

Leistung.
10.2 Wechselseitiger Ausschluss in Hardware
Wiederholung Betriebssystem bietet eine Abstraktion der Hardware an:
Dr. Brigitte Mathiak Kapitel 10 Physische Datenorganisation.
Beim Start eines Prozesses in Windows NT, 2000 wird a der Programmtext aus der exe-Datei ab der dort angegebenen Adresse gespeichert.
C Tutorium – Semaphoren –
PC-Cluster.
1 Spezielle Packages des Java SDK (1.4) java.nio.
Vs61 6 Verteilte Datenverwaltung. vs62 Ziel:Zusammengehöriger Datenbestand soll über mehrere Stationen verteilt werden, z.B. Fragmentierung: in mehrere.
Objektorientierter Entwurf (OOD) Teil 3: Qualitätsmodell
On a Buzzword: Hierachical Structure David Parnas.
Kapitel 6.1 Nebenläufigkeit und wechselseitiger Ausschluss
Kapitel 7.2 Dining philosophers problem
Ausnahmen HS Merseburg (FH) WS 06/07.
Universität Stuttgart Institut für Kernenergetik und Energiesysteme Was ist Refactoring? Bevor man die Integration angeht, mag es angebracht sein, den.
Threads Richard Göbel.
FH-Hof Deadlocks Richard Göbel. FH-Hof Deadlock - Definition Menge von Prozessen ist an einem Deadlock beteiligt: wenn jeder Prozess in dieser Menge auf.
Internetstruktur Das Internet besteht aus vielen Computern, die weltweit untereinander vernetzt sind.
XDoclet ETIS SS05.
Technik Gestaltung Navigation Daten. Übersicht Client Webbrowser InternetServer.
eXtreme Programming (XP)
Situationen Verteilte Anwendungen Wintersemester 06/07 © Wolfgang Schönfeld.
Access 2000 Datenbanken.
Vortrag III Hier in der Vorlesungszeit! Anwesenheitspflicht Jede Gruppe hat 6 Minuten! Stellt eure GUI vor –was ihr besonderes gemacht habt –Spektakuläre.
-LABORPRAKTIKUM- SOMMERSEMESTER 2005
DVG Klassen und Objekte
EDV Parallelprogrammierung1 Parallelprogrammierung mit JAVA.
© 2005 Pohlig - Taulien Datenströme GK Informatik 1 Datenströme.
Wizards & Builders GmbH Schulung Visual SourceSafe für Visual FoxPro Norbert Abb W&B.
Seite Common Gateway Interface. Konzepte. Übersicht 1Einleitung 2Was ist CGI? 3Wozu wird CGI verwendet? 4Geschichtlicher Überblick 5Grundvoraussetzungen.
Java Threads Sebastian Werler
Distributed Multimedia Control Steuerung und Überwachung von Präsentationen in Netzwerken.
Entwicklung verteilter eingebetteter Systeme - Einführung
Prof. Dr. Gerhard Schmidt pres. by H.-J. Steffens Software Engineering SS 2009Folie 1 Objektmodellierung Objekte und Klassen Ein Objekt ist ein Exemplar.
Duo- und Quad Prozessor-Architektur
Nestor Workshop im Rahmen der GES 2007 Digitale Langzeitarchivierung und Grid: Gemeinsam sind wir stärker? Anforderungen von eScience und Grid-Technologie.
Windows Befehlsskript
Gegenstand EDV Thema: Informative Webseiten
Programmierung paralleler Algorithmen mit MPI
Systeme 1 Kapitel 4 Prozesse WS 2009/10.
PSI - Überblick und Szenarien
Universität zu Köln Historisch-Kulturwissenschaftliche Informationsverarbeitung Softwaretechnologie II (Teil I): Simulation und 3D Programmierung Prof.
Quantum Computing Hartmut Klauck Universität Frankfurt WS 05/
Black Box Algorithmen Hartmut Klauck Universität Frankfurt SS
Parallel Programming Condition Queues
Thread Synchronisation in JAVA
Betriebssysteme Übung 2. Tutorium. Task 1 – Locks (1) Wozu Locks? Dienen dazu, exklusiven Zugriff auf eine Ressource sicherzustellen Lock = binäre Semaphore.
Objectives Verstehen was unterDelegate verstanden wird
Learning By Doing Parallelverarbeitung Multithreading (Nebenläufigkeit) Alte Idee der Parallelverarbeitung statt rein sequentieller Prozesse Parallelverarbeitung.
Systemsoftware und Betriebssysteme
Interprozess- kommunikation (IPC)
Einführung OpenSTA. Agenda - über OGVIT - Warum Lasttests - Was ist OpenSTA - Wie arbeitet OpenSTA - Skripte für OpenSTA - OpenSTA Collectors - Tests.
Parallelisierung für Multiprozessor-Maschinen Teil 2.
Parallelisierung für Multiprozessor-Maschinen
Javelin Internet-based parallel computing using Java.
2. Kommunikation und Synchronisation von Prozessen 2
Grundlagen, Prinzipien und Aufgaben eines Betriebssystems
Parallele Programmierung im.NET Framework Darmstadt, Präsentation am Beispiel von C-Sharp (C#)  Wichtige Grundlagen  Generika, Delegate, Lambda,
Webserver Apache & Xampp Referenten: Elena, Luziano und Sükran
6.3 Verteilte Transaktionen
LVM - Logical Volume Management unter Linux
Dieser Vortrag wird gesponsort von:
Der Taskmanager ist Bestandteil des Betriebssystems, der als Prozessmanager Prozessmanager unter anderem die aktuell laufenden Programme und Prozesse.
ResA am Arbeitsplatz Das Vorgehen ist angelehnt an „5 S“ und bietet Ihnen die Möglichkeit das Konzept der 5 Disziplinen ressourcenschonenden Arbeitens.
Spärliche Kodierung von Videos natürlicher Szenen Vortragender: Christian Fischer.
Multiprocessing mit OpenMPI Marius Albath. Vorlesung Betriebssysteme, Was ist OpenMPI Was ist OpenMPI OpenMPI Standard Setup OpenMPI Standard.
© 2008 TravelTainment The Amadeus Leisure Group Thread-Programmierung im Qt-Framework Von: Simon Lubberich Erstbetreuer:
Virtualisierung von Web-Applikationen mit Docker
 Präsentation transkript:

Paradigmen der Programmierung Nebenläufigkeit Prof. Dr. Christian Kohls Informatik, Soziotechnische Systeme 3. Synchronisation sequentieller und paralleler Aufgaben Erzeuger – Verbraucher - Muster Ringpuffer mit Semaphoren Phasenbezogene Mechanismen / Synchronisationspunkte Programme in Aufgaben organisieren Futures, Callables und Executors Thread Pools Performance und Skalierbarkeit

Erzeuger-Verbraucher-Muster Teller abtrocknen Geschirr sortieren Teller waschen Statistik führen Logfile schreiben Dokument analysieren Antwort berechnen Dateien einlesen Request empfangen …

Erzeuger-Verbraucher-Muster Teller abtrocknen Geschirr sortieren Teller waschen Statistik führen Logfile schreiben Dokument analysieren Antwort berechnen Dateien einlesen Request empfangen …

Erzeuger-Verbraucher-Muster Teller abtrocknen Geschirr sortieren Teller waschen Statistik führen Logfile schreiben Dokument analysieren Antwort berechnen Dateien einlesen Request empfangen …

Erzeuger-Verbraucher-Muster Teller abtrocknen Geschirr sortieren Teller waschen Statistik führen Logfile schreiben Dokument analysieren Antwort berechnen Dateien einlesen Request empfangen …

Erzeuger-Verbraucher-Muster Teller abtrocknen Geschirr sortieren Teller waschen

Erzeuger-Verbraucher-Muster Teller waschen Teller abtrocknen Geschirr sortieren Teller waschen

Erzeuger-Verbraucher-Muster Wenn die Produzenten zu wenig Arbeit generieren muss ggf. das Verhältnis geändert werden Achtung: Wenn Produzenten zuviel Arbeit generieren, die von den Konsumenten nicht bewältigt wird, dann muss die Produktion gedrosselt werden. Lösung: Bounded Queues – Begrenzte Warteschlangen (z.B. Ringspeicher)

Erzeuger-Verbraucher-Muster Ziele: Trennung von Aufgabenerstellung (Erzeugen) und Aufgabenausführung (Verbrauchen) Trennung zwischen unabhängigen Arbeitsschritten Platzieren von Arbeitsaufträgen in einer Todo-Liste für späteres Bearbeiten Vereinfacht die Entwicklung: Codeabhängigkeiten zwischen Produzent und Verbraucher werden reduziert Vereinfacht das Workload Management: Daten können mit unterschiedlicher Geschwindigkeit erzeugt/verbraucht werden können

Erzeuger-Verbraucher-Muster Varianten: Ein Produzent – Mehrere Verbraucher (z.B. Aufteilen der Liste in Einzelaufgaben, Dateien einlesen, Analyse verteilen) Mehrere Produzenten – Ein Verbraucher (z.B. Apfelkorb, Logfile, Zusammenführen von Teilergebnissen) Mehrere Produzenten – Mehrere Verbraucher (z.B. Web-Crawler: mehrere Seiten parallel Laden, mehrere Seiten parallel analyisieren) Beziehungen sind relativ: Eine Aktivität kann sowohl Verbraucher wie auch Erzeuger sein

Bounded Queues Mächtiges Resourcemanagementwerkzeug für zuverlässige Anwendungen Programme werden robuster Drosseln von Aktivitäten, die mehr Arbeit produzieren als bewältigt werden können Am Besten gleich zu Anfang einplanen und nicht erst wenn das System an die Grenzen stösst! Serielle Threadsicherheit durch Weitergabe des Objektbesitzes: Produzent gibt Objekt in Queue und sollte nicht länger darauf verweisen, Verbraucher nimmt Objekt aus Queue Nur Produzent ODER Verbrauchern besitzen eine Referenz auf das Objekt, so dass immer nur ein Thread darauf zugreift! Einsatzbereite Bibliotheksklassen: BlockingQueue als neuer Collectiontyp implementiert u.a. als ConcurrentLinkedQueue

Live Codebeispiele BoundedBuffer Implementierung mit Semaphoren Threadsichere „Apfelernte“ mit Java

Bessere Apfelernte Wettlaufsituation Threadsicherer Puffer

Semaphore (Funktionsweise) Semaphore sind eine atomare Operation, die sowohl für das Sperren kritischer Abschnitte, als auch für das Warten auf Bedingungen verwendet werden kann (wird daher auch in BS behandelt). Eine Semaphore (=Ampel) verwaltet eine Anzahl vor “Eintrittskarten” Konzept basiert auf 2 Operationen P (= proberen, testen) und V (= verhogen, erhöhen). P: wenn die Semaphore >0 (Eintrittskarten verfügbar) wird sie erniedrigt, stellt aber keine Barriere dar. Ist sie <= 0, so muss der Thread warten. (häufiger Name: acquire() ) V: die Semaphore wird um 1 erhöht (Eintrittskarte zurückgeben). Wenn dabei der Zähler auf 1 springt, wird ein wartender Thread freigegeben. (häufiger Name: release() ) Dieser Mechanismus heißt zählende Semaphore (Alternative: binäre Semaphore).

Semaphore (Einsatzgebiete) Semaphoren werden oft eingesetzt um zu begrenzen, wie viele Threads auf eine Ressource zugreifen können Ressourcennutzung zu beschränken Semaphoren mit nur einer Eintrittskarte sind (fast) wie Sperren (Locks) Unterschied: die Semaphore kann von anderen Threads freigeben werden (Threads besitzen die Semaphoren nicht!) Eine Semaphore kann als einzige Synchronisationsprimitive verwendet werden. Das sollte man aber nicht tun – Sie ist da sinnvoll, wo der Zugang zu einenm Codeabschnitt von einer bestimmten Anzahl abhängt (s. Beispiel)

Synchronisation Bisher betrachtet: Die Synchronisation von sequentiellen Aufgaben mit Produzent-Verbraucher-Muster mit Bounded Queues und Semaphoren Wie gehen wir mit parallelen Aufgaben um? Wie führen wir Teilergebnisse wieder zusammen? Beispiel Actors: Aufteilen von Listen und wieder zusammenführen

Datenparallelität und ForkJoin

Phasenbezogene Mechanismen Vielen nebenläufigen Lösungen ist gemeinsam, dass es immer bestimmte Punkte gibt, an denen die Abläufe mehrerer Threads abgestimmt werden müssen: Ein oder mehrere Threads warten darauf, dass eine Anzahl Threads einen bestimmten Zustand erreicht haben. Synchronisatoren haben Methoden, um diesen Zustand zu ändern, abzufragen und effizient darauf zu warten, dass der Zielstatus erreicht ist (keine Idle-Schleife!) CountDownLatch Zwei Threads synchronisisieren sich an bestimmten Stellen und tauschen dabei Daten aus. Latches warten auf Ereignisse CyclicBarrier Barrieren warten darauf, dass mehrere Threads einen Synchronisationspunkt erreichen, z.B. Simulationen oder Spiele

Live Code-Beispiele „Birthday Problem“ Berechnung durch Simulation statt statistischer Formel Viele Berechnungen notwendig Parallele Berechnung für verschiedene Gruppengrößen

Latches: Durchgangstore Verzögerung der Fortführung eines Prozesses bis ein neuer Status erreicht ist Funktioniert wie ein Tor: Solange der Endzustand nicht erreicht ist, ist das Tor geschlossen, niemand kann weiter Wenn Endzustand erreicht ist, dann werden alle Threads weitergelassen Sobald ein Tor offen ist (Endzustand erreicht), kann es nicht wiedergeschlossen werden (es muss ein neuer Latch erzeugt werden) Ziele: Sicherstellen, dass nicht weitergearbeitet wird bis andere (vorhergehende) Aktivitäten vollständig abgeschlossen sind Sicherstellen, dass eine Aktivität nicht startet bevor alle Resourcen initialisiert sind Sicherstellen, dass ein Service nicht startet bevor andere (von diesem Service benötigte) Services gestartet sind Warten bis alle anderen Aktivitäten bereit sind für den nächsten Schritt (z.B. Synchronisation von Simulationen oder Spielen)

CountDownLatch CountDownLatch ist eine flexible Implementierung für viele Situationen Erlaubt es einem oder mehreren Threads so lange zu warten bis eine bestimmte Anzahl Events aufgetreten ist (z.B. n Teilergebnisse vorliegen) countDown Methode dekrementiert den Counter wenn das Ereignis eingetreten ist der Zähler ist zu Beginn ungleich 0 await() blockiert bis der Zähler 0 ist (oder Wartezeit abgelaufen oder der wartenende Thread unterbrochen – interrupt – wird) Live Code-Beispiel

Futures in Scala Erinnern Sie sich an Actors aus dem Praktikum… Lösung mit Futures ist einfacher, da geblockt wird bis alle Teilergebnisse da sind (Warten auf das Ergebnis, also blockieren bis alle Ergebnisse da sind) Vorteil: Keine Probleme mit der Reihenfolge der Listen Problem: keine weiteren Ereignisse empfangen

Callables und Futures in Java Erlauben Methodenaufrufe in separaten Threads auszuführen. Oft im Zusammenhang mit weiteren Bibliotheksklassen (Executors, ExecutorService, FutureTask). Problem von Runnables: keine Rückgabewerte -> Lösung Callables Hier landen alle Exceptions im aufrufenden Thread !!

Futures in Java Das Verhalten von Future.get() hängt vom Status der Aufgabe ab: - wenn fertig (completed), dann gibt get() das Resultat sofort - sonst blockiert get bis die Aufgabe erledigt ist (completed Status erreicht) Callable kann man sich wie ein Runnable vorstellen, aber mit Result

Programme bestehen aus Aufgaben Fast alle nebenläufigen Anwendungen organisieren die Ausführung von Aufgaben Aufgaben werden nicht nacheinander in einem Thread (z.B. main) ausgeführt sondern nebenläufig Aufteilung in verschiedene, klar gekapselte Aufgaben hat folgende Vorteile: Vereinfachung der Programmorganisation (statt monolithischer Klötze) Fehlerbehebung ist einfacher aufgrund von Transaktiongsgrenzen Ermöglicht Nebenläufigkeit da parallele Bearbeitung einzelner, klar voneinander getrennter Aufgaben möglich ist.

Organisation eines Programms in Aufgaben Erster Schritt: Identifzierung sinnvoll abgegrenzter Aufgaben Ideal: Unabhängige Aufgaben, Arbeit hängt nicht ab von Zustand Rückgabewerten Seiteneffekten Anderen Aufgaben Je unabhängiger, desto nebenläufiger! Unabhängige Aufgaben können immer parallel ausgeführt werden wenn genug Ressourcen vorhanden sind! Kleine Aufgaben verbessern: Flexibilität für den Scheduler Besseres Load Balancing

Codebeispiel Webserver Single/Multithread Responsiveness / Reaktionsfreudigkeit: Wenn ein Request sehr lange blockiert sieht es so aus, als wenn der Service nicht mehr verfügbar ist (down). Passiert z.B. bei intensivem Zugriff auf Datenbanken oder Festplatte CPU ist nicht ausgelastet während auf I/O gewartet wird – dabei könnten schon weitere Anfragen beantwortet werden! Daher besser: mehrere Threads  1. Ansatz: für jede Verbindung ein Thread

Codebeispiel Webserver Single/Multithread

Paralleler Server Handler sind Threads, die bei Bedarf (geht vom Client aus) vom Server zur Erledigung einer Aufgabe gestartet werden. Sie übernehmen nach dem Start die restliche Kommunikation mit dem Client. Die Handler haben untereinander keine direkte Kommunikation. Sie benutzen oft gemeinsame Objekte (z.B. Datenbank)

Konsequenzen der Multithread-Lösung Aufgaben abarbeiten ist vom main Thread genommen, so dass dieser weiterarbeiten kann Neue Verbindungen können schneller bearbeitet werden Neue Verbindungen können bearbeitet werden, bevor andere Request abgearbeitet sind Aufgaben können parallel bearbeitet werden, mehrer Requests können gleichzeitig bedient werden  Bessere Antwortzeiten und besserer Durchsatz ABER: Taskhandling Code MUSS threadsicher sein! Z.B. durch lokale Objekte. Overhead durch Thread Lifecycle Management

Kosten des Multithreadings Threads verbrauchen zusätzliche Ressourcen, insbesondere Speicher! Stabilität: Es gibt ein Limit wie viele Threads erzeugt werden können (hängt von Parametern der JVM ab, und vom Speicher) Bis zu einem bestimmten Punkt erhöhen mehr Threads auch den Durchsatz. Ab dann verlangsamen weitere Prozesse jedoch die Anwendung! Und wieder gilt: dies mag beim Testen nicht vorkommen, aber bei Livesystemen, die lange laufen… Fazit: Problem beim Single-Thread: Sequentielle Abarbeitung führt zu sehr schlechten Antwortzeiten Problem beim Thread-Per-Task: schlechtes Ressourcenmanagement, Gefahr eines Crashes Daher: Statt Threads selbst erzeugen, lieber auf das Executor-Framework zurückgreifen

Executor Interface public interface Executor { void execute (Runnable command); }

Executor Interface Standardlösung für das Entkoppeln von Tasksubmission und Taskexecution! Aufgaben werden weiterhin als Runnable festgelegt Bonus: ExecutorService erweitert Executor und hat Lifecycle Methoden sowie Hooks für Statistiken, Verwaltung und Monitoring Basiert auf dem Produzent-Verbraucher-Muster Es gibt bereits Standardimplementierung von Executor

Einfaches Ändern der Execution Policies

Thread Pools Homogener Pool mit Arbeitsthreads Jeder Arbeitsthread kann (nacheinander) Aufgaben erledigen Es werden nicht ständig neue Threads erzeugt Wenn ein Thread stirbt (z.B. Fehler) kann ein neuer Thread erzeugt werden Aufgaben liegen in einer Warteschlange zum Abarbeiten Ein Arbeitsthread arbeitet einfach: Hole dir die nächste Aufgabe (das nächste Runnable) von der Warteschlange Führe die Aufgabe aus (rufe run() auf) Und so weiter (warte bis wieder Aufgaben in der Warteschlange sind)

Fabrikmethoden für Executors newFixedThreadPool: neue Threads werden erzeugt wenn neue Aufgaben reinkommen bis zu einer maximalen Thread-Anzahl, danach werden Threads wiederverwendet newCachedThreadPool: kein starres Limit sondern Anzahl der Threads wird nach „Bedarf“ angepasst – bei vielen Aufgaben viele Threads, bei wenigen Aufgaben werden Threads aufgegeben newSingleThreadExecutor: Ausführung der Aufgaben in einem einzigen Thread (keine Nebenläufigkeit) entsprechend der Warteschlangenstrategie (FIFO, LIFO, Priorität) newScheduledThreadPool: Thread Pool mit fixierter Größe, mit dem sich verzögerte und periodische Aufgaben organisieren lassen (ähnlich wie Timer, aber fehlerresistenter)

Thread Pools - Vorteile Wiederverwendung von Threads ist kostengünstiger als ständig neue Threads zu erzeugen und freizugeben Weniger Speicherbedarf! Nicht mehr für jede Aufgabe ein zusätzlicher Thread! Durch richtige Konfiguration erhält man immer genug Threads, um die CPU in Arbeit zu halten während man keine Speicherproblem hat MEHR Stabilität! „Degrades Gracefully“ Mehr Möglichkeiten für Tuning, Verwaltung, Monitoring, Logging, Fehlerreporting

Executor – Execution Policies Execution Policy für eine Aufgabengruppe lässt sich leicht ändern. Eine solche Policy legt fest, was, wo, wann und wie etwas ausgeführt wird: In welchem Thread wird die Aufgabe ausgeführt? In welcher Reihenfolge werden Aufgaben erledigt (FIFO, LIFO, Priorisiert)? Wie viele Aufgaben sollen max. nebenläufig bearbeitet werden? Bei Überlastung: welche Aufgaben sollen aussortiert werden und wie wird das System darüber benachrichtigt? Was soll vor/nach Ausführung einer Aufgabe zusätzlich geschehen? Wie werden Aufgaben abgebrochen/gecancelt? Fazit: Statt new Thread(runnable).start() lieber einen Executor dazwischen schalten!!! Das gibt viel mehr Flexibilität.

Performance Aufgepasst bei Datenparallelisierung von hetereogenen Aufgaben: Werden Aufgaben A und B zwischen zwei Workern aufgeteilt, aber A braucht 10x solange wie B, dann ist der Gesamtzuwachs an Geschwindigkeit für den Prozess ist nur 9%. Richtig gute Geschwindigkeitszuwächse erreicht man, wenn man viele unabhängige und homogene Aufgaben hat, die nebenläufig bearbeitet werden können. Richtige Poolgröße: Für rechenintensive (CPU-Nutzung) reicht ein Threadpool von NCPU+1 (selbst rechenintensive Aufgaben pausieren manchmal) Für I/O intensive Aufgaben sollten dagegen mehr Threads erzeugt werden, damit ein anderer Thread die CPU nutzen kann während I/O Operationen ausgeführt werden! Andere Ressourcen: Memory, FileHandler, Socket Handler, Datenbankanbindungen RTask = Wie viele Resourcen braucht jede Aufgabe? RAvailable = Anzahl vorhanden Ressourcen Anzahl sinnvoller Threads = RAvailable / RTask Bsp: wenn ich nur 10 DB-Anbindungen habe, dann bringen auch 20 Threads nicht mehr Datenbankverbindungen

Probleme mit Thread Pools Thread Pools funktionieren am Besten wenn die Aufgaben homogen und unabhängig sind! Das Vermischen von lange und kurzlaufenden Aufgaben führt zum Verklumpen/Verstopfen des Pools: Es kann sein, dass nur noch die lange laufenden Aufgaben am Zug sind und die kurzen Aufgaben ewig in der Warteschleife sind! Nur mit sehr vielen Threads ist dies vermeidbar. Der Pool muss groß genug sein, so dass Aufgaben, die voneinander abhängen, auch alle abgearbeitet werden können ohne abgelehnt oder warten zu müssen. Starvation Deadlocks: Warten auf das Ergebnis anderer Aufgaben, die noch in der Queue sind. Confinement: Aufgaben, die threasicherheit durch confinement herstellen dürfen nur NACHEINANDER ausgeführt werden! Diese Anforderungen sollten DOKUMENTIERT werden, damit nicht später bei der Wartung diese Anforderungen verletzt werden!

Performance allgemein Achtung: viele Techniken zur Performancesteigerung erhöhen die Komplexität Lesbarkeit und Wartbarkeit des Programmcodes verschlechtert sich oft Performancegewinn wird oft überschätzt Tatsächlicher Performancegewinn sollte gemessen und nicht geschätzt werden… Schlechte Konfiguration kann sogar Performanceverlust bedeuten! Bsp: Messen der Unterschiede für Birthday Problem Simulation

Skalierbarkeit Fähigkeit die Durchsatzrate bzw. Kapazität zu erhöhen wenn weitere Rechnerressourcen hinzugefügt werden (CPUs, Speicher, Datenspeicher, I/O Bandbreite). Problem: Viele der Tricks, die Performance in einer Single-Threaded-Umgebung zu erhöhen sind problematisch für die Skalierbarkeit. (z.B. Caching, Reordering von Befehlen) Verhältnis sequentieller und paralleler Aufgaben beeinflusst die Skalierbarkeit im Wesenlichen!  Amdahl's Law

Amdahl's Law Amdahl wollte seinerzeit zeigen, dass sich Parallelverarbeitung kaum lohnt. Das Gesetz sagt aus, dass die Geschwindigkeitssteigerung durch sequentielle Programmteile entscheidend begrenzt wird. 1 Speedup <= ( 1 – Seq ) N Seq + N = Anzahl Prozessoren Seq = Sequentieller Anteil des Programms Paralleler Anteil = 1 – Seq. Anteil

Beispiel: Ein Programm benötigt 20 Stunden auf einem Prozessor 95% lassen sich parallel bearbeiten, aber 1 Stunde (5 %) lässt sich nicht paralellisieren  Dann läuft das Programm mind. 1 Stunde und der maximale Speedup ist 20! Wenn wir einen Rechner mit N Prozessoren ausnutzen wollen, brauchen wir eine hohe Parallelisierbarkeit. Bei vielen Problemen (Simulation physikalischer Prozesse, Big Data) ist die Parallelisierbarkeit beliebig groß! Oft gilt: Sehr rechenintensive Anwendungen = sehr parallele Programme Aber „normale“ Anwendungen sind kaum parallelisierbar !

Versteckte Sequentialität Aufgaben aus der Warteschlange nehmen Warten auf Teilergebnisse und späteres Zusammenführen Warten auf dieselbe Sperre Alle nebenläufigen Anwendungen haben auch Punkte sequentieller Abhängigkeiten! Seqentialität hat negative Auswirkungen auf die Skalierbarkeit

Reduzierung des Wettlaufs um Sperren Neben logischen Abhängigkeiten ist die häufigste Ursache für Serialität das Warten mehrere Threads auf die gleiche Sperre Häufig wird dabei unnötig gewartet, da zu viel gesperrt wird… Wege diesen Wettlauf zu entschärfen: Sperren möglichst kurz nutzen – nur solange wie nötig Häufigkeit der Nutzung von Sperren reduzieren Für unabhängige Daten auch verschiedene Sperren nutzen Ausschließende Sperren durch andere Koordinationsmechanismen ersetzen (z.B. atomare Objekte, Thread Confinement, ReadWriteLocks)

Granularität von Sperren synchronized auf Methoden vermeiden: Es wird ein zu langer Block gesperrt Auch unabhängige Ressourcen werden gesperrt (andere synchronized Methoden können nicht aufgerufen werden, auch wenn diese unabhängige Variablen verändern) Lock Splitting: Verschieden Sperren für voneinander unabhängige Variablen Lock Stripping: Verschiedene Sperren für voneinander unabhängige Datenbereiche (z.B. Arrays nicht mit einer Sperre sondern n Sperren schützen) Nachteil: mehr Sperren erhöhen die Gefahr von Deadlocks aufgrund zirkulärer Abhängigkeiten Implizites Lock Splitting durch delegieren an threadsichere Implementierungen, z.B. threadsichere Set Implementierung

Threadsichere Container Die Containerklassen in java.util sind in der Regel nicht threadsicher (z.B. HashMap)! Man kann aber für viele Fälle ganz einfach ein sicheres Objekt erzeugen: Collections.synchronizedMap Collections.synchronizedList usw. Map<String, Person> m = Collections.synchronizedMap(new HashMap<String, Person>()) Geschützt sind allerdings nur die elementaren Operationen (put, get). Wenn mehrere Operationen zwingend zusammengehören, ist eine entsprechende Synchronisation nötig! Insbesondere muss die Anwendung eines Iterator per synchronized geschützt werden: Set<String> s = m.keySet(); synchronized (m) { for (String x : m) .... Performance Einbußen aufgrund hohen Grads der Serialisierung! Bessere Performance bieten die java.util.concurrent.* Collections, z.B. java.util.concurrent.ConcurrentMap

Ausblick Frameworks: Actors für Scala und Java mit akka Play Framework Servlets Node.js (serverseitiges JavaScript)

Kosten des Multithreadings Koordination zwischen Threads: Sperren, Nachrichtenaustausch, Speichersynchronisation Kontextwechsel kostet Zeit Threads erzeugen und abwickeln Scheduling Overhead Sinnvoles Multithreading bedeutet, dass der Performancezuwachs höher als die Kosten ist Ziel des Multithreadings: Bessere Performance durch Effektivere Nutzung der zur Verfügung stehenden Ressourcen (kein Leerlauf der CPU) Einbinden zusätzlicher Ressourcen wenn diese zur Verfügung stehen (mehrere CPUs auch nutzen) Performance bedeutet: wie schnell und wie viel? Für Serveranwendung ist das „wie viel“ meist wichtiger als das „wie schnell“.