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

Mutexe

von MitgliedThePuppetMasterSeite 1 von 1

Der Begriff Mutex ist eine Abkürzung für den englischen Ausdruck Mutual Exclusion, was so viel wie "gegen- oder wechselseitiger Ausschluss" bedeutet. Mutexe sind sehr hilfreich, wenn mehrere Externer Link!Threads nahezu gleichzeitig z.B. auf globale Variablen zugreifen könnten. Dies könnte zu inkonsistenten Daten und somit Fehlern führen. Um dies zu verhindern, kann die gemeinsam genutzte Ressource mit einem Mutex als "gerade in Benutzung" kennzeichnet werden. Während dieser Zustand besteht (=Mutex gesperrt ("locked")), müssen andere Threads damit warten, auf diese Ressource zuzugreifen. Erst wenn der Thread, der sich die Ressource "geschnappt" und das Mutex gesperrt hat, sie wieder freigibt, das Mutex also entsperrt ("unlock"), kann ein anderer Thread ebenso verfahren.

Mutexe sind also bei der Programmierung mit mehreren Threads in FreeBASIC von Bedeutung. Wer keine Threads verwendet, braucht auch keine Mutexe. ;-)

Gehen wir nun aber "in medias res": Zuerst einmal sollte man wissen, dass ein Mutex in FreeBasic nur ein BefehlsreferenzeintragPointer ist, der als "Any Ptr" definiert wird.

Dim Shared MyMutex as Any Ptr

In diese Variable speichern wir anschließend ein neu erstelltes Mutex, das wir später verwenden können.

MyMutex = MutexCreate()

Mit dieser Variable können wir nun also das Mutex ansprechen und es "sperren" ...

MutexLock(MyMutex)

oder "entsperren".

MutexUnLock(MyMutex)

Tipp: Um detaillierte Informationen zu den Befehlen MutexCreate, MutexLock und MutexUnlock zu erhalten, können Sie in den Quelltexten einfach auf das jeweilige Schlüsselwort klicken, woraufhin Sie zum passenden Eintrag in der Befehlsreferenz gelangen.

Gehen wir einmal davon aus, dass wir ein Programm geschrieben haben, das 2 Threads verwendet. Jeder dieser Threads möchte auf eine Variable zugreifen und diese verändern.

Dim Shared MyVar as UInteger

Das Problem hierbei ist jedoch, dass 2 PARALLEL ablaufende Threads nachezu GLEICHZEITIG auf diese Variable zugreifen KÖNNTEN und auch können.
Als Beispiel wählen wir einmal eine Demonstrationsprogramm, das nichts anderes tut, als zu überprüfen, was für ein Wert sich in MyVar befindet, und anschließend diesen Wert ausgibt.

Aber erstellen wir erstmal einen Thread, der ständig nichts anderes tut, als einen Wert in dieser Variable zu ändern.

Sub MyThread_Wechsler()
For X = 1 to 100                'Wir durchlaufen das ganze 100 mal
    If MyVar = 1 Then           'Wenn MyVar den Wert 1 hat dann
        MyVar = 0               'Wert zu 0 ändern
    Else                        'ansonsten
        MyVar = 1               'Wert zu 1 ändern
    End If
    Sleep 100, 1
Next
End Sub

Dieser Thread simuliert nun ein Ereignis, das zwar ständig eintritt, aber von dem wir nicht genau wissen, wann es eintritt, da dieser Thread mit dem nächsten Thread parallel ablaufen wird.

Der nächste Thread wird jetzt überprüfen, welchen Wert die Variable besitzt, und daraufhin eine "eins" oder eine "null" + den Wert der Variable ausgeben.
Jedoch kann es hier zu einem folgenschweren Problem kommen, das ich mal direkt im Sourcecode beschreibe:

Sub MyThread_Pruefer()
For X = 1 to 100                'Wir durchlaufen das ganze 100 mal
    If MyVar = 1 Then           'Hier prüfen wir zuerst einmal ob die Variable eine 1 enthält
        Print "Eins: "; MyVar   'wenn dem so war, dann geben wir als text "Eins" aus sowie den Wert in
                                'der Variable.
                                'Und genau hier ist das Problem!
                                'Es kann gut sein, das der Wert in MyVar, 1 beträgt. Allerdings kann es nun
                                'auch passieren, das genau nach der If Prüfung der vorherige Thread den Wert
                                'zu 0 ändern. Dadurch würde natürlich die ausgabe bei Print nicht mehr stimmen.
    Else
        Print "Null: "; MyVar
    End If
    Sleep 100, 1
Next
End Sub

Um dieses Fehlverhalten zu unterbinden, setzen wir das Mutex ein. Dazu müssen wir jedoch beide Threads leicht erweitern.

Zuerst einmal den "Wechsler"-Thread:

Sub MyThread_Wechsler()
For X = 1 to 100                'Wir durchlaufen das ganze 100 mal
    MutexLock(MyMutex)          'Hier haben wir nun eine Sperre eingebaut.
                                'Diese Funktion prüft intern zuerst einmal, ob ein anderer Thread zuvor
                                'das MyMutex gespert hat. Wenn dies der Fall war, dann bleibt dieser Thread
                                'solange in der Funktion stehen, bis der vorherige Thread das MyMutex entsperrt
                                'hat. Wenn dieser die Entsperrugn vorgenommen hat, prüft die Funktion ob noch
                                'andere Threads, vor diesem hier, ein MutexLock aufgerufen haben. Wenn dem so ist
                                'dann werden natürlich erst diese freigegeben.
                                '(Frei nach dem Motto: Wer zuerst Kommt mahlt zuerst)
                                'Wurden alle vorherigen Threads abgearbeitet sidn wir an der Reihe. Die Funktion
                                'MutexLock gibt uns nun frei, und wir können mit der Variable arbeiten.
                                'Threads, die nach uns das MutexLock aufgerufen haben, bleiben jetzt alle stehen.
                                'und zwar so lange, bis wir das Mutex wieder entsperren.

    If MyVar = 1 Then           'Bevor wir die Entsperrung durchführen, prüfen wir zuerst einmal welchen
                                'wert die variable hat, auf die auch andere Threads zugreifen möchten.
        MyVar = 0               'Hatte sie den Wert 1, dann ändern wir diese zu 0
    Else                        'ansonsten
        MyVar = 1               'zu 1
    End If
    MutexUnLock(MyMutex)        'Nachdem wir all unsere aufgaben mit dieser Variable durchgeführt haben
                                'können wir das Mutex wieder frei geben, und somit anderen Threads erlauben
                                'weiter zu arbeiten.
    Sleep 100, 1                'wir warten aber noch 0.1sek, bevor wir das Ganze erneut durchlaufen.
Next
End Sub

Anschließend müssen wir auch den zweiten Thread bearbeiten:

Sub MyThread_Pruefer()
For X = 1 to 100                'Wir durchlaufen das ganze 100 mal
    MutexLock(MyMutex)          'Auch hier sperren wir das Mutex, bevor wir auf etwas zugreifen, das auch andere
                                'Thread vor haben.
                                'Wenn wir an der reihe sind, können wir auf die Variable zugreifen
                                'und dessen Wert auslesen, und oder verändern.

    If MyVar = 1 Then           'Hier prüfen wir zuerst einmal ob die Variable eine 1 enthält
        Print "Eins: "; MyVar   'wenn dem so war, dann geben wir als text "Eins" aus sowie den Wert in
                                'der Variable.
                                'Nun haben wir nicht mehr das Problem, das ein anderer Thread den Wert ändern
                                'könnte, da dieser Thread in MutexLock fest sitzt, solange wir das MyMutex nicht
                                'entsperrt haben. Wir könnten uns also so viel zeit lassen wir wir möchten, ohne
                                'zu befürchten, dass ein anderer irgendetwas macht, das uns Probleme bereiten könnte.
    Else
        Print "Null: "; MyVar
    End If
    MutexUnLock(MyMutex)        'Auch wir müssen nach getaner arbeit das Mutex freigeben, damit die restlichen Threads
                                'weiter arbeiten können.
    Sleep 100, 1
Next
End Sub

Das Prinzip des MutexLock / -UnLock lässt sich nicht nur auf Variablen anwenden. Ganz im Gegenteil!
Es ist so konzipiert, das jegliche Arbeiten geregelt werden können. Primär dient es dazu, gleichzeitige Zugriffe von Threads auf das gleiche zu unterbinden, da diese Zugriffe wie bereits erwähnt Fehler hervorrufen können.

WICHTIG IST AUCH ZU BEACHTEN, dass nicht nur der Schreib-, sondern auch der Lesevorgang geschützt werden muss!!!

Ein...

Print MyVar

... in 2 parallelen Threads kann zum selben Chaos führen wie das Schreiben in eine Variable ...

MyVar = 10

Es sollte also unbedingt ein MutexLock Verwendung finden, bevor auf Variablen oder sonstige Funktionen zugegriffen wird, die von derselben Variable etwas möchten. Sei es lesend oder schreibend!

Wir könnten zum Schluss dieses Tutorials natürlich ein Beispiel schreiben. Jedoch ist es leider nicht vorhersagbar, ob das Beispiel einen Fehler produziert oder nicht. Das ist eines der Tücken von Threads.
Dadurch, dass sie parallel aber nicht völlig synchron ablaufen, weiß man nie, ob sie gerade gleichzeitig auf etwas zugreifen oder man Glück hat und sie dies im richtigen Augenblick nicht tun.

Selbstverständlich ist ein Programm im Übrigen nicht auf ein Mutex beschränkt. Es können nahezu beliebig viele Mutexe erzeugt, gesperrt oder entsperrt werden. Jedoch ist dabei unbedingt darauf zu achten, dass in einem Thread nicht 2x hintereinander ein MutexLock oder ein MutexUnLock erfolgt! Nach einem MutexLock darf auch nur einmal ein MutexUnLock erfolgen. Erst danach darf wieder ein MutexLock ausgeführt werden. Sonst könnte ein Thread natürlich andere Mutexsperren aufheben, obwohl deren Urheberthreads noch gar nicht mit der Arbeit fertig sind.

Und nun viel Erfolg mit dieser neuen Technik!

 

Zusätzliche Informationen und Funktionen
  Bearbeiten Bearbeiten  

  Versionen Versionen