Multithreading mit .NET Jetzt besonders günstig in allen Sprachen Bernd Marquardt Software & Consulting [email protected].

Download Report

Transcript Multithreading mit .NET Jetzt besonders günstig in allen Sprachen Bernd Marquardt Software & Consulting [email protected].

Multithreading mit .NET
Jetzt besonders günstig in allen Sprachen
Bernd Marquardt
Software & Consulting
[email protected]
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 MultiThreading
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 HauptThread
•

Ein Thread ist ein
„Ausführungsweg“ im Programm
•

Wird vom Betriebssystem gestartet
Ein Thread führt ganz bestimmte
Befehle nacheinander aus
In einem Programm können
mehrere unabhängige Threads
existieren
Kontrollfluss
 Synchrone
A()
Abarbeitung
B()
A()
 Asynchrone
C()
D()
B()
C()
D()
E()
Abarbeitung
E()
F()
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 einem 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
•
•
•
Vordergrund-Threads
•
•
•
Werden automatisch beendet, wenn der
Haupt-Thread beendet wird
Anwendungen: Aufräumarbeiten, langwierige
Berechnungen
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
8
Y = SQRT(X)
7
6
∆X
5
4
3
H
2
1
0
0
10
20
30
40
50
Erzeugung von Threads

Worker-Thread mit statischen Methoden
•
•
•
•

ThreadStart-Klasse zeigt auf die ThreadFunktion
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 Sychronisierungsproblem:
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
EinprozessorRechner
ZweiprozessorRechner
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 AbortBefehl 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 MultiThreading testen
•

Dann erst die Softwareteile in die MTApplikation einfügen
•


Z.B.: mathematische Algorithmen
Nun kann man den SynchronisierungsCode testen
Verfahren ist nicht immer möglich
Unbedingt mit MehrprozessorRechnern 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 wartet
auf die
Resource
Thread B hat die
Resource in
Benutzung
Thread B wartet
auf 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 CodeDurchlaufs 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 Daten
 ManualResetEvent
 AutoResetEvent
„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 Monitor
 Nicht 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-Sperren
 Objekt der Klasse ReaderWriterLock
muss angelegt werden
• Z.B. als private member der fraglichen
Threadklasse
• AcquireReaderLock / ReleaseReaderLock
• AcquireWriterLock / ReleaseWriterLock
 Geschachteltes
Locking
• UpgradeToWriterLock
Timer
 System.Threading.Timer
 Periodische
Ausführung von
Funktionalität
 Status wird in übergebener ObjektInstanz gehalten
 Deinitialisierung mit Dispose()
MT und Windows Forms

Windows Forms laufen in einem STAKontext
•
•
•
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 MDIAnwendung) 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 PoolThread 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 ThreadPool 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(
<paramListe der Methode>,
AsyncCallback aDelegate,
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>,
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...