Date post: | 05-Apr-2015 |
Category: |
Documents |
Upload: | diederick-zeller |
View: | 105 times |
Download: | 1 times |
Multithreading mit .NETJetzt besonders günstig in allen Sprachen
Bernd Marquardt
Software & Consulting
Agenda
Einführung Erzeugung von Threads
• Parameterübergabe, Performance Steuerung von Threads
• Priority, Suspend, Resume, Abort Debugging Synchronisierung Der Thread-Pool Threads und Lokalisierung Zusammenfassung
Einführung
Multi-Threading kommt jetzt aus der .NET-Runtime
• Es ist kein Sprach-Feature (wie z.B. in C++)
• Jede .NET-Sprache kann Multi-Threading
• Im .NET-Framework gibt es mehrere Klassen, die das Arbeiten mit Threads sehr vereinfachen• Siehe: Thread, ThreadStart , ThreadPool,
Monitor, Mutex, Interlocked,…
Was ist ein Thread?
Ein Programm besteht mindestens aus einem Thread, dem Haupt-Thread
• Wird vom Betriebssystem gestartet
Ein Thread ist ein „Ausführungsweg“ im Programm
• Ein Thread führt ganz bestimmte Befehle nacheinander aus
In einem Programm können mehrere unabhängige Threads existieren
Kontrollfluss
Synchrone Abarbeitung
Asynchrone Abarbeitung
A() B() C() D() E()
A() B()
D()
F()
C()
E()
F()
A() B() C() D() E()
A() B()
D()
F()
C()
E()
F()
Was ist ein Thread?
Jeder Thread hat seinen eigenen Stack
Threads können jedoch auch globale Resourcen des Programms gemeinsam nutzen
Jeder Thread hat eine bestimmte Priorität
• Das Betriebssystem vergibt über die Priorität entsprechende Zeitscheiben für die Ausführung an den Thread
Threads & Prozessoren
In einem Rechner mit mehreren Prozessoren können entsprechend viele Threads tatsächlich gleichzeitig laufen
Ein Rechner mit zwei Prozessoren ist aber nicht doppelt so schnell, wie sein Ein-Prozessor-Kollege
ACHTUNG: Win95, 98 und ME können nur mit einemeinem Prozessor arbeiten
Einsatz mehrerer Threads
Wann kann man mehrere Threads sinnvoll einsetzen?
• Bei intensiven, mathematischen Berechnungen
• Grafik-Algorithmen
• Drucker-Ausgabe
• Aufräumarbeiten im Hintergrund
• Übertragen von Daten im Hintergrund
• ......
Kleine Warnung
Die Programmierung von Applikationen mit mehreren Threads ist kompliziert
• Debugging, Fehlersuche, Testen
• Synchronisierung
• Ordentliches Beenden von Threads
• Fehlerbehandlung
• Parameterübergabe
• Saubere Planung und Prioritätenvergabe
Vorab-Analyse
Bevor man mehrere Threads programmiert, sollte man prüfen:
• Ist eine saubere Abtrennung der Aufgaben des Threads möglich?
• Ist eine vernünftige Datenübergabe möglich?
• Kann die Applikation überhaupt andere Aufgaben ausführen, wenn der zusätzliche Thread läuft?
• Muss im neuen Thread auf Resourcen zugegriffen werden, die sowieso schon blockiert sind?
Thread-Typen I
Man kann grob zwei Typen von Threads unterscheiden:
• Worker-Threads...• ...führen mathematische Berechnungen
im Hintergrund aus
• User-Interface-Threads...• ...sorgen für die flüssige Bedienung eines
Windows-Programms
Beide Typen sind sich sehr ähnlich UI-Threads erfordern mehr Planung
Thread-Typen II
Andere Unterscheidung:
• Hintergrund-Threads• Werden automatisch beendet, wenn der
Haupt-Thread beendet wird
• Anwendungen: Aufräumarbeiten, langwierige Berechnungen
• Vordergrund-Threads• Der Haupt-Thread wartet, bis alle anderen
Vordergrund-Threads beendet sind
• Anwendungen: Eigener Thread für ein Fenster
• Steuerung mit Property „Thread.IsBackground“
Unser Beispiel
Aus der numerischen Mathematik
• Bestimmung der Fläche unter einer Kurve
• Numerische Integration
• Hier: Wurzel-Funktion
Bei kleiner Schrittweite hat man lange Rechenzeiten
Berechnungen können leicht in einem oder mehreren Threads durchgeführt werden
Unser Beispiel
0
1
2
3
4
5
6
7
8
0 10 20 30 40 50
H
∆X
Y = SQRT(X)
Erzeugung von Threads
Worker-Thread mit statischen Methoden• ThreadStart-Klasse zeigt auf die Thread-
Funktion
• Thread-Klasse erlaubt die Steuerung des Threads
• Thread.Join wartet auf den Thread
• Thread.Join geht auch mit einem Timeout• Abfrage, ob Timeout, ist möglich
Ist der Thread gestartet mit „IsAlive“• Bedeutet nicht, dass der Thread auch läuft
Problem mit 2 Threads
Ein typisches Sychronisierungs-problem:
Zwei Threads sollen quasi-gleichzeitig die Fläche unter der Kurve berechnen
• Achtung: Es gibt nur eine Variable „dSum“ Statischer Kontext des Threads
• Falsches Rechenergebnis!!!
• Das Verhalten mit statischen Variablen kann aber auch erwünscht sein, z.B. Zählen von Threads
Erzeugung von Threads
Thread-Methoden in instanzierten Klassen
Vorteil: Datenübergabe ist kein Problem
• Jeder Thread hat seine eigene Instanz der Thread-Klasse
Nachteil: Diese Vorgehensweise kostet etwas mehr Resourcen
• Performance und Speicher
Parameterübergabe
Alle Variablen werden als „private“ in der Thread-Klasse deklariert
Es gibt eine spezielle Funktion in der Thread-Klasse, welche...
• ...die Daten übernimmt
• ...das Thread-Objekt erzeugt und zurückgibt
Das Rechenergebnis wird über ein Property der Thread-Klasse abgefragt
Performance
Einprozessor- Zweiprozessor-Rechner Rechner
Mit 1 Thread 119,078 sek 119,979 sek
Mit 2 Threads 119,091 sek 59,875 sek
Mit 3 Threads 119,088 sek 59,922 sek
Mit 10 Threads 119,090 sek 59,859 sek
Mit 1000 Threads --- 60,218 sek
Performance
Rechner:• Dual Pentium III, 500 MHz, 500 MB RAM
• Pentium III, 600 MHz, 200 MB RAM
Thread-Erzeugung und Kontextwechsel scheinen sehr schnell zu sein
Frage: Wieviele Kontextwechsel hat es gegeben???
Bei ca. 3000 Threads: OutOfMemoryException
Es werden nicht immer sofort alle Threads gestartet
Steuerung von Threads
Prioritäten-Vergabe mit Thread.Priority
Suspend und Resume
• WIN32: n x Suspend = n x Resume
• .NET: n x Suspend = 1 x Resume
Abort
• Man muss feststellen können, ob der Thread seine Arbeit beendet hat• Möglichkeit: Bool‘sche Variable
WANN genau der Thread beeinflußt wird, kann man nicht vorhersagen
Steuerung von Threads
Wann hält der Thread an, wenn ein Abort-Befehl kommt?• Im Normalfall wird der Thread NICHT sofort
stehenbleiben• Single-Prozessor-Maschine: Beendigung der
Zeitscheibe des laufen den Threads
• Die Runtime steuert einen sog. „Safe-Point“ an• Das gilt auch bei „Suspend“
• Am „Safe-Point“ hat der Garbage-Collector die volle Kontrolle über das Thread-Objekt
• Wenn „unmanaged“ Code ausgeführt wird, muss dieser erst beendet werden
Steuerung von Threads
Ein Thread kann sich aus seiner Thread-Methode auch selbst „suspenden“
ACHTUNG: Ein anderer Thread muß ihn dann wieder „resumen“
Gefahr von Deadlocks ist hier gegeben
Steuerung von Threads
Bei „Abort“ wird immer eine ThreadAbortException geworfen
In der Thread-Prozedur wird die Exception gefangen und es kann „aufgeräumt“ werden
Problem: Ein Thread der „suspended“ ist, reagiert nicht auf „Abort“ und er kann dann auch nicht mehr „resumed“ werden!!!
• Lösung: Test des ThreadStates
Steuerung von Threads
Es gibt leider noch ein Problem: Wenn eine Thread-Methode mit Abort
unterbrochen wird, wenn sie gerade einen finally-Block ausführt, dann wird der finally-Block nicht bis zu Ende bearbeitet
Es wird sofort (wenn vorhanden) der catch-Block der ThreadAbortException bearbeitet
Steuerung von Threads
„ResetAbort“ in einer ThreadAbortException
• Der Thread kann dann weiterlaufen
Frage: Wo läuft der Thread weiter?
• Nach dem catch-Zweig der Exception
• NICHT nach der Stelle, an der das Abort den Code unterbrochen hat!
Debugging
Debugging mit VS .NET möglich Liste aller Threads über „Debug >
Windows > Threads“• Einfrieren eines Threads möglich
• Bestimmten Thread weiterlaufen lassen
• Zeilenweise Debuggen
Debugging von MT-Applikationen ist evtl. sehr mühselig
Tests sind aufwändig, da man alle parallelen Zustände (durch Threads) irgendwie testen „sollte“
Debugging
Zuerst möglichst viel ohne Multi-Threading testen
• Z.B.: mathematische Algorithmen
Dann erst die Softwareteile in die MT-Applikation einfügen
• Nun kann man den Synchronisierungs-Code testen
Verfahren ist nicht immer möglich Unbedingt mit Mehrprozessor-
Rechnern testen!
Synchronisierung
Die Synchronisierung soll den Zugriff auf begrenzte Resourcen des Rechners steuern, wenn mehrere Threads benutzt werden
• Variablen (RAM)
• Codeteile
• LPT-, COM- und USB-Schnittstellen
Synchronisierung
Eine Synchronisierung beinhaltet immer die Gefahr eines „Deadlocks“
Thread A wartetauf die
Resource
Thread B hat dieResource inBenutzung
Thread B wartetauf Thread A
Synchronisierung
Objekte für die Synchronisierung• Monitor
• Mutex
• Events
• Interlocked
• WaitHandle
• ReaderWriterLock Im .NET-Framework gibt es Klassen
für diese Synchronisations-Objekte
Gleichzeit. Code-Zugriff
Verhinderung des gleichzeitigen Code-Durchlaufs mit Monitor-Objekt
Synchronisierung eines bestimmten Code-Bereiches, so dass nur ein Thread den Code benutzt
• Andere Threads müssen warten
Das Monitor-Objekt kann nur innerhalb eines Prozesses verwendet werden
Zugriff auf eine Variable kann mit einem Monitor-Objekt gesteuert werden
Gleichzeit. Code-Zugriff
Bei Monitor: Angabe des Objektes, das geschützt werden soll
• ACHTUNG bei:
• Normalen Value-Typen (int, double, structs)
• Strings
Hier funktioniert der Monitor nicht
• Wegen Boxing/Unboxing
• Wegen neuer String-Referenzen (wenn mit dem String etwas „gemacht“ wurde)
Synchron. mit Mutex
Das Mutex-Objekt funktioniert in .NET wie in WIN32
Es ist prozessübergreifend einsetzbar
• Durch Festlegen eines Namens
Darum ist das Mutex-Objekt langsamer, als ein Monitor-Objekt
Bis zu 64 Mutex-Objekte sind verfügbar (plattformabhängig)
Synchron. mit Events
Dienen der Koordinierung der Aktionen mehrerer Threads untereinander• Im Gegensatz dazu: Monitor, Mutex
verhindern den gleichzeitigen Zugriff auf Resourcen
Sicherstellung, dass die Threads ihre Aufgaben in der richtigen Reihenfolge durchführen• Beispiel: Ein Thread schreibt in einen Puffer,
ein anderer Thread liest die DatenManualResetEventAutoResetEvent
„Atomare“ Anweisungen
Viele Befehle dürfen nicht unterbrochen werden
„Interlocked“-Klasse garantiert die vollständige Ausführung bestimmter Grundbefehle, wenn mehrere Threads auf die Variablen zugreifen können:
• Increment
• Decrement
• Exchange
• CompareExchange
WaitHandles
Ähnliches Konzept zu MonitorNicht auf Managed Code beschränkt
• WaitHandle kapselt Plattform-Ressource
• Ermöglicht Synchronisation mit Unmanaged Code
Drei Typen:
• Mutex
• AutoResetEvent
• ManualResetEvent
Andere Sync.-Methoden
Serialisierter Zugriff auf ArrayLists mit „Synchronized“• Benutzung eines thread-sicheren Wrappers
• Gibt es für viele .NET-Objekte (Queue, SortedList, Stack,…)
Synchronisierung ganzer Methoden mit dem Attribut [MethodImpl(MethodImplOptions.Sychronized)]
• Funktion ähnlich wie Monitor
• Das Attribut wirkt immer auf die gesamte Methode
ReaderWriterLock
Monitore unterscheiden nicht zwischen lesenden und schreibenden Threads
Analogie: Datenbank-SperrenObjekt der Klasse ReaderWriterLock
muss angelegt werden• Z.B. als private member der fraglichen
Threadklasse
• AcquireReaderLock / ReleaseReaderLock
• AcquireWriterLock / ReleaseWriterLockGeschachteltes Locking
• UpgradeToWriterLock
Timer
System.Threading.TimerPeriodische Ausführung von
FunktionalitätStatus wird in übergebener Objekt-
Instanz gehaltenDeinitialisierung mit Dispose()
MT und Windows Forms Windows Forms laufen in einem STA-
Kontext• Nur der Thread, der das Fenster erzeugt
hat, darf auf Methoden des Fensters und der Controls zugreifen
• „lock“ darf NICHT auf Fenster und Controls angewendet werden• Gefahr von Deadlocks
• Benutzung von „Invoke“ und „BeginInvoke“ zur Umgehung der Probleme• Ist langsamer, als ein direkter Aufruf
• Wichtig: Der Aufruf läuft NICHT in einem neuen, separaten Thread!
MT und Windows Forms
Anwendung: Jedes Fenster (in einer MDI-
Anwendung) läuft in einem separaten Thread
Probleme: In den Threads können Exceptions
auftreten
• Der Haupt-Thread kann dann ungültiger Referenzen auf nicht mehr existierende Fenster-Threads enthalten
• Aufwändige Programmierung
Threads & Lokalisierung
Startet man einen neuen Thread, dann benutzt er die default-Resourcen
ResourceManager kann normal benutzt werden
Kultur (oder Objekt „CultureInfo“) muss in den Thread als Parameter übergeben und benutzt werden
Der .NET-Thread-Pool
Ziel: Möglichst einfaches Arbeiten mit mehreren Threads• Optimal für Threads, die lange Zeit auf etwas
warten
• Optimal für Threads, die periodisch erweckt werden
Der Thread-Pool stellt “fertige” Threads bereit, die genutzt werden können
Im Thread-Pool werden mehrere Threads für den Benutzer bereit gehalten
Aufruf aus dem Thread-Pool ist schneller, als die normale Thread-Erzeugung
Der .NET-Thread-Pool
Achtung: Man kann nur eine bestimmte Anzahl von Pool-Threads nutzen
• Abfrage, wieviele Threads sind maximal möglich: ThreadPool.GetMaxThreads
• Abfrage, wieviele Threads sind jetzt noch möglich: ThreadPool.GetAvailableThreads
• Wird versucht, mehr Threads als erlaubt, zu erzeugen, so landen die überzähligen Threads in einer Warteschlange
Property „IsThreadPoolThread“
Der .NET-Thread-Pool
Systemkonzept: Pro Prozess ein ThreadPool
Anforderungen an den Pool werden über eine Warteschlange gestellt
Muss eine Anforderung länger als eine halbe Sekunde warten, wird neuer Pool-Thread erzeugt
• Maximal jedoch 25
Wird Pool-Thread 30 Sekunden nicht benötigt, wird Thread aus dem Pool entfernt
Der .NET-Thread-Pool
Die .NET-Runtime benutzt den Thread-Pool ebenfalls:
• Asynchrone Aufrufe
• Socket-Verbindungen
• I/O-Completion Ports
• Timer
Benutzung eines Pool-Threads mit ThreadPool.QueueUserWorkItem
• Von unmanaged Code: CorQueueUserWorkItem
Der .NET-Thread-Pool
Jeder Pool-Thread bekommt einen Standard-Stack, die Standard-Priorität und läuft im MTA
Wann sollte man den Thread-Pool nicht benutzen?
• Wenn der Thread eine bestimmte Priorität haben soll
• Wenn der Thread lange Zeit laufen soll und evtl. andere Threads blockieren kann
• Wenn man den Thread immer genau identifizieren will (zur Steuerung,…)
BeginInvoke()
Bereitet den Methodenaufruf vor:
• Reicht Werte- und Referenzparameter weiter
• Setzt übergebenen Callback auf (optional)
• Reicht Statusobjekt durch
• Gibt IAsyncResult zurück
Signatur:
IAsyncResult BeginInvoke( IAsyncResult BeginInvoke( <paramListe der Methode>, <paramListe der Methode>,
AsyncCallback aDelegate, AsyncCallback aDelegate, object state) object state)
IAsyncResult BeginInvoke( IAsyncResult BeginInvoke( <paramListe der Methode>, <paramListe der Methode>,
AsyncCallback aDelegate, AsyncCallback aDelegate, object state) object state)
EndInvoke()
Schließt die Operation ab:
• Liest alle Ausgabe- und Referenzparameter aus
• Wird auf IAsyncResult aus BeginInvoke() angewendet
• Liefert den Rückgabewert der aufgerufenen Methode
• Kann in rufender Methode oder dem Callback aufgerufen werden
bool EndInvoke(<paramListe der Methode>,bool EndInvoke(<paramListe der Methode>, AsyncCallback aDelegate) AsyncCallback aDelegate)
bool EndInvoke(<paramListe der Methode>,bool EndInvoke(<paramListe der Methode>, AsyncCallback aDelegate) AsyncCallback aDelegate)
Synchron. mit Callback
Warten, bis ein asynchroner Aufruf beendet ist• Warten auf IAsyncResult.AsyncWaitHandle
• WaitHandle wird implizit gesetzt, wenn EndInvoke() aufgerufen wird
VORSICHT!• Wird Callback angegeben und wartet der
Hauptthread auf das WaitHandle, dann wird bei Beendigung der asynchronen Methode• WaitHandle signalisiert
• Und Callback aufgerufen
• Keine Garantie über Reihenfolge!
Weitere Beispiele WinCancel: Durchführung einer
zeitaufwändigen Berechnung in WinForms TimerTest: Benutzung von Timern MatrixMT: Matrix-Multiplikation mit MT und
ThreadPool SolveGS: Lineares Gleichungssystem mit
mehreren Threads lösen MTMDI4: Multithreading mit mehreren
Fenstern WinDemo: Spielprogramm mit Threads IO_Calc_MT: IO und Berechnungen in
mehreren Threads parallel ausführen
Zusammenfassung
Multiple Threading ist mit .NET etwas einfacher geworden
Multiple Threading ist nicht mehr abhängig von einer bestimmten Programmiersprache
Bevor man loslegt:• Analyse, ob MT wirklich benötigt wird
Debugging und Testen ist schwieriger Code wird etwas aufwändiger (z.B. durch
Synchronisierung) Auch sehr wichtig für „Sanduhr-freie“
Applikationen mit User-Interface
Fragen!?
Uff...Uff...