Multithreading mit.NET Jetzt besonders günstig in allen Sprachen Bernd Marquardt Software &...

Preview:

Citation preview

Multithreading mit .NETJetzt besonders günstig in allen Sprachen

Bernd Marquardt

Software & Consulting

berndm@go-sky.de

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...

Recommended