Tutorial
Threading
von ThePuppetMaster | Seite 2 von 5 |
Es stellt sich natürlich die Frage, was an einem Thread überhaupt sinnvoll erscheint, wenn man die Nachteile mit den Vorteilen abwägt.
Meines erachtens ist ein Thread nicht immer sinnvoll. Es kommt explizit darauf an, was zu tun ist und wie viele Ressourcen in "Mitleidenschaft" gezogen werden.
Ein schönes Beispiel von Multithreading gibt ein Netzwerkserver.
Sind beispielsweise 3 Clienten mit dem Server verbunden, zu denen jeweils 1MB übertragen werden soll, wird ohne Multithreading jeder Client nacheinander abgearbeitet.
Dies ist bei einem Thread zwar auch gegeben, jedoch wird hier anders vorgegangen. Ein Singlethread Programm würde zum senden eine API Funktion aufrufen.
Diese Funktion würde die Daten zum Netzwerkstack schicken. Anschließend wartet die Funktion auf die Bestätigung das alle Daten erfolgreich beim Client angekommen sind.
Diese Wartezeit richtet sich nach der Internetverbindung des Clienten. Hat er eine sehr langsame Verbindung, dann dauert dies relativ lang, im vergleich zu einer schnelleren Verbindung.
Genau hier kann ein multithreaded Programm Vorteile haben. Hier würde jeder Thread ebenfalls nacheinander die zu sendenden Daten in den Netzwerkstack ablegen.
Jeder Thread, welcher die Funktion aufgerufen hat, befindet sich nun in der Warteschleife und wartet für sich auf die Bestätigung. Eine Schnellere Leitung würde jetzt deutlich schneller eine Antwort bekommen.
Dadurch kann dieser Thread weiter arbeiten, ohne warten zu müssen, das ein anderer Client mit dem senden fertig ist.
Beim Singlethread würde dies deutlich langsamer sein, da nach dem senden das selbe Phänomen bei jedem Client auftreten würde.
Ein Weiteres Beispiel für effektives Multithreading ist die Erzeugung eines Videos. Soll z.B. ein Video aus Bildern in ein AVI gewandelt werden, wobei Bilder beschnitten und verkleinert werden sollen, kann hier ebenfalls ein Vorteil herausgearbeitet werden.
Der Vorteil richtet sich hier jedoch eher auf Multicore und Cluster-Computer als auf ein Singlecore mit Multithreading.
Jeder CPU-Kern erhält ein eigenes Bild mit der Aufgabe diese zu bearbeiten. Dadurch arbeiten mehrere Kerne gleichzeitig an mehreren Bildern. Der Vorteil ist hier Bilder/sek * CPU Anzahl.
Nach dem bearbeiten schicken die Kerne die neuen Daten an das Hauptprogramm zurück welches die neuen Bildern in das Video integriert.
Wie sich Zeigt ist ein Multithreading eher für spezialisierte Aufgaben geeignet und keinesfalls für jedes Programm.
Man sollte sich daher erst einmal auf die Konzeptionierung kümmern sowie auf das eigentliche Ziel eines Programms.
Als Beispiel für dieses Tutorial nehme ich ein einfaches Programm, das eine relativ komplexe Bildbearbeitung durchführen soll.
Hierfür benötigen wir zuerst einmal irgend ein größeres Bild (300 x 300 Pixel)
Dieses wird geladen und im Speicher hinterlegt
#include once "fbgfx.bi" 'Grafikunterstützung includieren
Dim Shared Bildname as String 'Dateiname des Bildes
Dim Shared Quellbild as FB.Image 'Speicherpointer auf das geladene Bild
Dim Shared Zielbild as FB.Image 'Speicherpointer auf das neue Bild
Bildname = "bild.bmp" 'Bildname festlegen
ScreenRes 800, 600, 32 'Grafik initialisieren
Dim XFN as Integer = FreeFile 'Filenummer einhohlen
Dim TBW as UInteger 'Temporäre Variable für Bildbreite
Dim TBH as UInteger '-||- für Bildhöhe
'Bild öffnen um Größe zu erfahren
If Open(Bildname For Binary Access Read As #XFN) 0 Then Screen 0: Print "Error while loading pictures!": End -1 'Durch fehlerhaften Ladevorgang Programm beenden
Get #XFN, 19, TBW 'Bildbreite einlesen
Get #XFN, 23, TBH 'Bildhöhe einlesen
Close #XFN 'Datei schließen
Quellbild = ImageCreate(TBW, TBH, 32) 'Quellbild mit eingelesener Dimension erzeugen
Zielbild = ImageCreate(TBW, TBH, 32) 'Zielbild mit eingelesener Dimension erzeugen
If BLoad(Bildname, Quellbild) 0 Then Screen 0: Print "Error while loading pictures!": End -1 'Beim fehlerhaften Bildladen Programm beenden
'...
Der nächste Schritt sieht vor ein funktionsfähigen Thread zu schreiben, welcher einen Bildausschnitt bearbeitet.
Der Thread muss laut unserer Konzeptionierung fähig sein eigenständig diese Verarbeitung vorzunehmen.
Zuerst einmal der Thread, dieser benötigt einen Pointer auf die Informationen.
Sub Bearbeitugnsthread(V_Data as Any Ptr)
'Der Thread kann (muss nicht) im Hauptprogramm schneller erzeugt werden, als es braucht um die Informationen zu speichern
'daher wird im Thread ein Mutexlock durchgeführt, um zu verhindern, das Daten beschädigt werden
MutexLock(XMutex) 'Zuerst das Mutex sperren.
Dim Info as Thread_Type Ptr = Cast(Thread_Type Ptr, V_Data) 'DatenPointer zwischenspeichern
MutexUnLock(XMutex) 'Sperre wieder aufheben
End Sub
Bis hierher sind noch alle arbeiten problemlos per Multithreading möglich. Es entstehen keine Probleme beim Zugriff auf Speicherbereiche.
Der Thread hat einen Pointer auf einen Teil des Bildes, welchen wir dem Thread von unserem Hauptprogramm aus zuweisen werden.
Damit es keine Probleme bei diesem Zugriff gibt, kopieren wir die Daten zuvor in einen separaten Speicherbereich damit der Thread eigenständig hantieren kann.
Unser Beispiel erzeugt alle Threads auf einmal. Natürlich ist es auch möglich die Anzahl Threads zu beschränken, z.B. auf 100 um die Auslastung zu minimieren
Da jedoch unser Bild relativ klein ist, können wir alle Threads gleichzeitig initialisieren.
'...
If BLoad(Bildname, Quellbild) 0 Then Screen 0: Print "Error while loading pictures!": End -1 'Beim fehlerhaften Bildladen Programm beenden
Dim XW as UInteger = TBW / 20 'Anzahl Segmente ausrechnen. Ein Segment = 20x20 Pixel
Dim XH as UInteger = TBH / 20 '-||-
Type Thread_Type 'dieser Typ hält Informationen zu einem definierten Thread
V_State as UByte 'Statusvariable
V_Image as FB.Image Ptr 'Der Pointer auf das zu bearbeitenden Teilbild
V_Thread as any Ptr 'Thread Pointer
V_PosX as UInteger 'Position des Teilbildes
V_PosY as UInteger '-||-
End Type
Dim Shared XMutex as any Ptr = MutexCreate() 'Mutex variable und Mutex erzeugen
Dim XThreadMax as UInteger 'Variable für Maximalanzahl der Threads
Dim XThread() as Thread_Type 'Ein Array welches die Thread Pointer festhält
Dim TBildPtr as FB.Image Ptr 'Eine Temporäre Variable für den Bildpointer
Dim X as UInteger = 1 'Variable für die aktuelle Position des Bildsegmentes
Dim Y as UInteger = 1 '-||-
Dim Z as UInteger 'Eine temporäre Variable
Dim XID as UInteger '-||-
XThreadMax = 25 'Anzahl gleichzeitiger Threads auf 25 beschränken
Redim XThread(XThreadMax) as Thread_Type 'Array vorbereiten
'Segmente Threads erzeugen
Do
XID = 0 'ID auf 0 Setzen.
MutexLock(XMutex) 'Array Sperren, solang zugriff darauf erfolgt
'Zuerst prüfen, ob fertige Element vorliegen (Thread ist fertig = 255)
For Z = 1 To XThreadMax 'Alle Arrayelemente durchgehen
If XThread(Z).V_State = 255 Then 'Element fertig?
With XThread(Z)
Threadwait(.V_Thread) 'dann auf das ende des Threads warten
'Nachdem der Thread fertig ist, muss das bearbeitete Bild in das Zielbild kopiert werden
'Dabei geben die zuvor zwischengespeicherten Positionsdaten die Position im Zielbild an
Put Zielbild, (.V_PosX * 20, .V_PosY * 20), .V_Image, PSET
ImageDestroy(.V_Image) 'Temporäres Bild wieder löschen
.V_State = 0 'und Element wieder als "frei" markieren
End With
End If
Next
'Anschließend nach einem freien Element suchen
For Z = 1 To XThreadMax 'Alle Arrayelemente durchgehen
If XThread(Z).V_State = 0 Then 'Element frei?
XID = Z 'Dann freie ID speichern
Exit For 'und suche verlassen
End If
Next
If XID > 0 Then 'Wurde ein freies Element gefunden?
TBildPtr = ImageCreate(20, 20, 32) 'Wir erzeugen einen Speicherbereich für ein Teilbild
'da jeder Thread sein eigenes Teilbild erhält, muss auch für jeden Thread ein eigener Pointer erzeugt werden.
'Anschließend bereiten wir die Daten vor
Put TBildPtr, (0, 0), Quellbild, (X * 20, Y * 20)-((X + 1) * 20, (Y + 1) * 20), PSET 'Ein Teilbereich des Bildes kopieren
MutexLock(XMutex) 'Da ab hier auf das Array zugegriffen wird, was ebenfalls auch aus dem Thred heraus geschieht, müssen wir das Array "sichern"
With XThread(XID)
.V_State = 1 'Zuerst setzten wir im Array die Statusvariable. Dadurch das Element als "in Nutzung (1)" definiert
.V_Image = TBildPtr 'Den Pointer auf das Teilbild speichern
.V_PosX = X 'Position des Teilbildes speichern
.V_PosY = Y '-||-
.V_Thread = ThreadCreate(Cast(Any Ptr, @Bearbeitugnsthread), Cast(Any Ptr, @XThread(XID))) 'Threaderzeugen
End With
'Erst wenn alle Schreiboperationen ins Array komplett sind (inklusive Threadpointer) darf das Mutex wieder entsperrt werden
MutexUnLock(XMutex) 'anschließend können wir die Sperre wieder aufheben.
'Nachdem der Thread erzeugt wurde kann das nächste Teilbild ausgewählt werden
X += 1 'Nächstes horizontales Element wählen
If X > XW Then 'Ist nächstes Element außerhalb des Bildbereichs
Y += 1 'dann nächstes vertikales Element wählen
XW = 1 'Und horizontales Element auf Anfang setzen
If Y > XH Then Exit Do 'Ist das vertikale Element außerhalb des Bildes, dann Schleife verlassen
End If
End If
Sleep 1, 1
Loop
'Nachdem kein Teilbild mehr abgearbeitet werden soll (bzw. alle fertig sind), auf das Ende aller Threads warten
MutexLock(XMutex) 'Array sperren
For Z = 1 To XThreadMax 'Alle Arrayelemente durchgehen
If XThread(Z).V_State > 0 Then 'Element noch in arbeit?
With XThread(Z)
Threadwait(.V_Thread) 'dann auf das Ende des Threads warten
'Nachdem der Thread fertig ist, muss das bearbeitete Bild in das Zielbild kopiert werden
'Dabei geben die zuvor zwischengespeicherten Positionsdaten die Position im Zielbild an
Put Zielbild, (.V_PosX * 20, .V_PosY * 20), .V_Image, PSET
ImageDestroy(.V_Image) 'Temporäres Bild wieder löschen
.V_State = 0 'und Element wieder als "frei" markieren (Obligatorisch)
End With
End If
Next
MutexUnLock(XMutex) 'Sperre wieder aufheben
'Zum Schluss das Ergebnis mit dem Original darstellen
Put (0, 0), Quellbild, PSET
Put (400, 0), Zielbild, PSET
MutexDestroy(XMutex) 'Mutex Zerstören
XMutex = 0 'Das auf "0" setzen der MutexVariable ist ZWINGEND!!!!! erforderlich
'Würde dies nicht getätigt, und noch ein Thread laufen, welcher auf ein Mutex zugreift
'Kommt es hierdurch zu einer Blockade im MutexLock. und das Programm würde dauerhaft stehen bleiben!
Sleep 'Auf einen Tastendruck warten, bevor das App beendet wird
End 0 'Programm sauber beenden
Hiermit wäre die eigentliche Strukturierung fertig. Das Hauptprogramm sorgt durch seinen Mutex dafür, das sowohl Thread als auch das Hauptprogramm selbst nicht auf Ressourcen zugreifen kann, die mehrfach in Nutzung sind.
Durch die weitergabe des Pointer auf die Informationsstruktur an den Thread, muss im Thread selbst auch dafür gesorgt werden, das Zugriffe auf diese Informationen geschützt ablaufen.
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|
|