Buchempfehlung
Mikrocomputertechnik mit Controllern der Atmel AVR-RISC-Familie
Mikrocomputertechnik mit Controllern der Atmel AVR-RISC-Familie
Umfassend, aber leicht verständlich führt dieses Buch in die Programmierung von ATMEL AVR Mikrocontrollern ein. [Mehr Infos...]
FreeBASIC-Chat
Es sind Benutzer im FreeBASIC-Chat online.
(Stand:  )
FreeBASIC bei Twitter
Twitter FreeBASIC-Nachrichten jetzt auch über Twitter erhalten. Follow us!

Tutorial

Threading-Optimierung

von MitgliedThePuppetMasterSeite 6 von 6

Der nächste "Angriffspunkt" bei thredbasierter Optimierung ist der ungeschützte wechselseitige Zugriff auf gemeinsame Ressourcen im Lesebetrieb.

Grundsätzlich sei gesagt, das Schreibzugriffe auf gemeinsame Ressourcen IMMER geschützt werden müssen, und es hier keinen Weg daran vorbei gibt. Dies gilt ebenfalls für lesende Aufrufe.
Einzigste Möglichkeit der Optimierung stellt hier eine komplexe Speicherverwaltung dar, welche über einen komplexen Speicherbereich den Zugriff auf die selben Ressourcen, quasi klonend, ermöglicht.
Wichtig ist hier zu erwähnen, das solche Optimierungen ausschließlich die Geschwindigkeit berücksichtigen und den Speicherverbrauch gänzlich vernachlässigen bzw. Ignorieren.

Angenommen es existieren mehrere Threads, welche deutlich häufiger auf ein und die selbe Ressource lesend zugreifen als schreibend. Dann wäre es von Vorteil ein Mechanismus zu entwickeln, der es ermöglicht ohne Mutexe den Lesevorgang abzuwickeln.
Ein Solches Prinzip möchte ich hier erarbeiten und vorstellen.

Zuerst einmal ist ein Thread nötig, welcher relativ häufig auf gemeinsame Ressourcen lesend zugreift und nur relativ selten schreibende Operationen durchführt.

Dim Shared GlobVar as UInteger

Sub Thread()
Do
    MutexLock(XMutex)
    If GlobVar = 10 Then
        GlobVar = 15
    ElseIf GlobVar = 100 Then
        GlobVar = 115
    ElseIf GlobVar = 1000 Then
        GlobVar = 1015
    End If
    MutexUnLock(XMutex)
    Sleep 1, 1
Loop
End Sub

Das Hauptprogramm hingegen ändert nach einer zufälligen Zeit die Variable ab.

Dim C as UInteger
Dim X as UInteger
Do
    C += 1
    If X < C Then
        MutexLock(XMutex)
        GlobVar += 1        MutexLock(XMutex)
        C = 0
        X = Int((Rnd * 1000) + 1)
    End If
    Sleep 1, 1
Loop

Dieser Thread liest ständig "GlobVar" aus und überprüft ob "GlobVar" einen bestimmten Wert angenommen hat. Ist dies der Fall, dann wird "GlobVar" abgeändert.
Dabei liest der Thread weitaus öfter "GlobVar" aus als er X setzt. Hierbei wird jedoch viel Zeit verschwendet, während das Mutex gesperrt ist.

Es wäre also hilfreich, wenn eine Möglichkeit bestünde "GlobVar" nicht sperren zu müssen um dessen Zustand zu prüfen. Führt man sich einmal den Ablauf dieses Codes vor Augen, erkennt man das Potenzial "GlobVar" einfach auszulagern.
Diese "Auslagerung" ist jedoch nicht einfach und erfordert einiges an zusätzlichem Codeaufwand. Dieser rechtfertigt sich jedoch nur bei wirklich Zeitintensiven Programmaufgaben, dessen Threads "relativ" lange Laufzeiten besitzen.

Zuerst wird der Code für die wechselseitige Zugriffsverwaltung erzeugt.

'Eine Typenstruktur erzeugen. Sie hält Informationen für den Redirect bereit.
Type Memory_Type
    V_Next      as Memory_Type Ptr
    V_Prev      as Memory_Type Ptr
    V_Source    as Any Ptr
    V_Redirect  as Any Ptr
    V_Mutex     as Any Ptr
End Type
'Linkedlist zur Speicherverwaltung des Managements
Dim Shared Memory_First as Memory_Type Ptr
Dim Shared Memory_Last as Memory_Type Ptr
Dim Shared Memory_Mutex as Any Ptr


'Sub welche eine Variable mit einer globalen Variable definiert.
Sub Memory_Regist(V_SourceVar as Any Ptr, ByRef V_DestVar as Any Ptr, ByRef V_VarMutex as Any Ptr)
MutexLock(Memory_Mutex)
If Memory_Last  0 Then
    Memory_Last->V_Next = CAllocate(SizeOf(Memory_Type))
    Memory_Last->V_Next->V_Prev = Memory_Last
    Memory_Last = Memory_Last->V_Next
Else
    Memory_Last = CAllocate(SizeOf(Memory_Type))
    Memory_First = Memory_Last
End If
With *Memory_Last
    .V_Source   = V_SourceVar
    .V_Redirect = V_DestVar
    .V_Mutex    = MutexCreate()
    V_VarMutex  = .V_Mutex
End With
MutexUnLock(Memory_Mutex)
End Sub


'Sub welche einen Wert global speichert. Diese Sub muss entsprechend Überladen werden, wenn andere Datentypen genutzt werden sollen.
Sub Memory_Set(V_DestVar as Any Ptr, ByRef V_Value as UInteger)
MutexLock(Memory_Mutex)
Dim XSource as Any Ptr
Dim TPtr as Memory_Type Ptr = Memory_Last
Do Until TPtr = 0
    If TPtr->V_Redirect = V_DestVar Then XSource = TPtr->V_Source: Exit Do
Loop
If XSource = 0 Then MutexUnLock(Memory_Mutex): Exit SubTPtr = Memory_Last
Do Until TPtr = 0
    If TPtr->V_Source = XSource Then
        MutexLock(TPtr->.V_Mutex)
        *Cast(UInteger Ptr, TPtr->V_Redirect) = V_Value
        MutexUnLock(TPtr->.V_Mutex)
    End If
Loop
MutexUnLock(Memory_Mutex)
End Sub

Diese Strukturierung hinterlegt in einer Linkedlist den Zielpointer mit dem Quellpointer.

Der Thread muss hier natürlich entsprechend angepasst werden.

Sub Thread()
Dim LocVar as UInteger
Dim XMutex as Any Ptr
Memory_Regist(@GlobVar, @LocVar, XMutex) 'Hier wird die Globale Variable, die gemeinsam verwendet werden soll, mit der Lokalen Variable assoziiert.
Do
    MutexLock(XMutex)
    If LocVar = 10 Then
        Memory_Set(LocVar, 15)
    ElseIf LocVar = 100 Then
        Memory_Set(LocVar, 115)
    ElseIf LocVar = 1000 Then
        Memory_Set(LocVar, 1015)
    End If
    MutexUnLock(XMutex)
    Sleep 1, 1
Loop
'Vor dem Ende mus die Variablen-regestrierung wieder aufgehoben werden!
'Ansonsten werden beim "Memory_Set" Speicherzugriffsfehler auftreten!
End Sub

Hier entfällt das Mutex für GlobVar vollständig da der nötige Schutz in der entsprechenden Memory_Set Sub schon integriert ist und Leseaktionen über separate Speicherbereiche ablaufen.
Zum lesen wird eine Lokale Variable genutzt, welche in der Linkedlist mit der Globalen "verlinkt" wird. Die globale Variable dient hier ausschließlich der Identifikation, und nicht mehr dem Zugriff.
Wird nun der Wert der lokalen Variable per Memory_Set geändert, wird der neue Wert automatisch in alle anderen registrierten Variablen hinterlegt.
Um die Lokale Variable zu lesen nutzt man ein Seperates Mutex, das ausschliesslich für diese Variable vorhanden ist.
Ansich ist es ein massiver Speicherverbrauch. Man könnte auch den Eindruck gewinnen das die abarbeitung noch langsamer ist als mit nur einem Mutex. Das ist jedoch nicht ganz richtig.
Dadurch, das jede Lokale Variable ein eigenes Mutex besitzt wird diese variable auch nicht durch andere Lesende Threads beeinflusst oder Blockiert.
Das Mutex welches hier verwendugn findet, dient ausschliesslich dem Schutz bei Schreibzugriffe auf die Variable. Zu anderen Zeiten (Lesende Zeiten) ist die Variable ausschliesslich für einen Thread gültig und wird so nich behindert.

Beim aufruf von "Memory_Set" wird das entsprechende Mutex der Lokalen Variable nur für die kurze schreibaktion Blockiert. In dieser Zeit können jedoch alle anderen Threads mit dem Wert Ihrer Variable weiter arbeiten.
Die Änderung für andere Threads tritt erst dann ein, wenn dessen Variable aktualisiert wurde.

Auf keinen Fall sollte man das DeRegistrieren der Lokalen Variable aus der LinkedList bevor der Thread sich beendet. Sonst kann ein anderer Thread, der über "Memory_Set" Daten verändert auf Variablen zugreifen, die sich nicht länger im Stack des Threads befinden.
Dadurch würd es zu einem Speicherzugriffsfehler kommen, da die Variable, welche im Thread erzeugt wurde, nach dem Ende des Threads nicht länger existiert.


Damit wären wir auch schon am Ende dieses Tutorials.


MfG + HF
TPM

 

Gehe zu Seite Gehe zu Seite  1  2  3  4  5  6  
Zusätzliche Informationen und Funktionen
  Bearbeiten Bearbeiten  

  Versionen Versionen