Mehrfaches Lock-/UnlockMutex im selben thread

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von Kurzer »

Offenbar kann man LockMutex und UnlockMutex innerhalb des gleichen Threads mehrmals aufrufen.
(btw: Das geht aus der Hilfe leider nicht so richtig hervor. Könnte man noch mit aufnehmen)

Ich habe einige Prozeduren, die verschachtelt sind, aber ggf. auch separat aufgerufen werden. Von daher sperrt sich jede Prozedur selbst mittels Lock/UnlockMutex. Das geht soweit gut, auch wenn sich mehrere dieser Prozeduren mehrfach selbst aufrufen.

Wenns dann irgendwo aber mal hängt suche ich nach einer Debuggermöglichkeit, um die Stelle herauszufinden wo evtl. ein Unlock vergessen wurde. Die Debuggerfunktionen und Hilfsfenster habe ich ehrlich gesagt bisher nicht so richtig bis in die Tiefe genutzt. Gibt das der Debugger her, dass man solche Fehler schnell auffindet?

In meinem Fall ist es leider nicht so, dass man das Problem einfach so nachstellen kann. Hier wird ein Customgadget (module) sowohl von einem thread "bedient" als auch gleichzeitig manuell über die GUI von mir. Und irgendwann gibts dann einen deadlock wo alles einfriert. Eine manuelle Fehlersuche wird an der Stelle zu einem ziemlichen rumstochern.

Nachtrag: Ich erweitere meine Frage mal etwas, da ich gerade ein Verständnisproblem habe
Es geht um ein Customgadget mit sagen wir mal folgenden Funktionen:
- Create
- Draw
- Free

Das Ganze ist in einem Module realisiert und die drei Prozeduren sind public.
Da das Customgadget mit mehreren Instanzen genutzt werden können soll, wird bei Create() intern eine Gadgetstruktur allokiert, in der alle nötigen Parameter gespeichert werden.

Das könnte z.B. so aussehen:

Code: Alles auswählen

		*GraphGadget = AllocateStructure(GraphGadget)
		If *GraphGadget <> 0
			With *GraphGadget
				\iGadgetX = x
				\iGadgetY = y
				\iGadgetHeight = iGadgetHeight
				\iGadgetWidth = iGadgetWidth
				\iGadgetBorderSize = iGadgetBorderSize
				\iGadgetBorderColour = iGadgetBorderColour
				... usw

Wird das Gadget mittels Free() wieder entfernt, dann wird der allokierte Strukturspeicher dieser Instanz wieder freigegeben.

Code: Alles auswählen

		; Parametercheck
		If *GraphGadget = 0
			ProcedureReturn 
		EndIf
		
		With *GraphGadget
			; irgendwelche Aufräumarbeiten
		EndWith
		
		FreeStructure(*GraphGadget)
Nun hatte ich es so realisiert, dass ich pro Instanz einen Mutex angelegt habe, der die internen Prozeduren gegen Mehrfachaufruf schützen soll. Die öffentliche Funktion Draw() könnte ja von mehren Threads gleichzeitig für die selbe Gadgetinstanz aufgerufen werden.

Das hat nichts gebracht, es kam dabei trotzdem zum deadlock (irgendwann). Was mir einleuchtet ist, dass ich, um an den Mutex zu gelangen, erst einmal in die Struktur des betreffenden Gadget greifen muss.

Also so z.B.:

Code: Alles auswählen

	Procedure   Draw(*GraphGadget.GraphGadget)
		; Parametercheck
		If *GraphGadget = 0 : ProcedureReturn : EndIf
		
		; Wait until the Gadget is unlocked
		LockMutex(*GraphGadget\iMutex)
		
		; Mach was....
		
		UnlockMutex(*GraphGadget\iMutex)
	EndProcedure
Das heißt, dass beim Griff in die Struktur bei LockMutex() die Struktur noch nicht geschützt ist. Wenn ein zweiter thread gerade das gleiche versucht, wird es krachen.

Daher dachte ich mir, dass ich für das gesamte Modul einen Globalen Mutex benutze.

Code: Alles auswählen

Global.i iMutex
Wie managed man das jetzt am besten, um unabhängig von den Instanzen des Customgadgets herauszubekommen, wann der Mutex wieder freigegeben werden muss? Und wann/wo erzeuge ich den Mutex?

Ich könnte je eine globale Init() und DeInit() Prozedur schreiben, die man vor Erzeugung der ersten Instanz bzw. nach Freigabe der letzten Instanz des Customgadgets aufrufen muss, aber das kommt mir zu ungeschmeidig vor.

Man könnte noch einen globalen Mutex-Zähler mitlaufen lassen, der in Create() und Free() jeweils angepasst wird. Aber ist das der richtige Weg, wenn man ein multiaufrufsicheres Customgadget schreiben möchte?
Zuletzt geändert von Kurzer am 15.03.2019 23:28, insgesamt 2-mal geändert.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Useralter in 2024: 56 Jahre.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6999
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Lock/UnlockMutex freezes per Debugger auffinden

Beitrag von STARGÅTE »

Kurzer hat geschrieben:Ich habe einige Prozeduren, die verschachtelt sind, aber ggf. auch separat aufgerufen werden. Von daher sperrt sich jede Prozedur selbst mittels Lock/UnlockMutex. Das geht soweit gut, auch wenn sich mehrere dieser Prozeduren mehrfach selbst aufrufen.
Zumindest geht das solange gut, solange die Procedure (selbst) immer auch ein Unlock() ausführt und nicht etwa durch ein ProcedureReturn "frühzeitig" beendet wird.
Kurzer hat geschrieben:Wenns dann irgendwo aber mal hängt suche ich nach einer Debuggermöglichkeit, um die Stelle herauszufinden wo evtl. ein Unlock vergessen wurde.
Du kannst dir ein Macro mit gleichem Namen schreiben, und dann in diesem Macro Quellcode Informationen abrufen:
#PB_Compiler_Line, #PB_Compiler_Procedure usw.
Diese können dann an eine eigene Procedure übergeben werden, die alle LockMutex() mitloggt und ggf. mit einem Counter prüft, in welcher Stufe es klemmt.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Lock/UnlockMutex freezes per Debugger auffinden

Beitrag von Kurzer »

STARGÅTE hat geschrieben:
Kurzer hat geschrieben:Ich habe einige Prozeduren, die verschachtelt sind, aber ggf. auch separat aufgerufen werden. Von daher sperrt sich jede Prozedur selbst mittels Lock/UnlockMutex. Das geht soweit gut, auch wenn sich mehrere dieser Prozeduren mehrfach selbst aufrufen.
Zumindest geht das solange gut, solange die Procedure (selbst) immer auch ein Unlock() ausführt und nicht etwa durch ein ProcedureReturn "frühzeitig" beendet wird.
Ja, das ist auf jeden Fall gegeben. Das habe ich geprüft.
STARGÅTE hat geschrieben:Du kannst dir ein Macro mit gleichem Namen schreiben, und dann in diesem Macro Quellcode Informationen abrufen:
#PB_Compiler_Line, #PB_Compiler_Procedure usw.
Diese können dann an eine eigene Procedure übergeben werden, die alle LockMutex() mitloggt und ggf. mit einem Counter prüft, in welcher Stufe es klemmt.
Das ist eine gute Idee, danke. :allright:

Allerdings habe ich meine Frage gerade per Edit ausgeweitet, evtl. "deadlockt" es ja aus anderen Gründen, weil ich die falsche Herangehensweise nutze? Evtl. kannst Du Dir mein Edit noch ansehen?
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Useralter in 2024: 56 Jahre.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6999
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von STARGÅTE »

Die Herangehensweise mit Create und Free ist verständlich, mache ich auch so.
Kurzer hat geschrieben:Nun hatte ich es so realisiert, dass ich pro Instanz einen Mutex angelegt habe, der die internen Prozeduren gegen Mehrfachaufruf schützen soll. Die öffentliche Funktion Draw() könnte ja von mehren Threads gleichzeitig für die selbe Gadgetinstanz aufgerufen werden.
Hier stellt sich mit die Frage wieso das passieren sollte? CustonGadgets können mit BindEvent alle Events abfangen und auch nur genau bei diesen Events sich neu zeichnen. Von "außen" also public sollte der Draw() befehl eigentlich nicht ausgeführt werden. Wie auch immer kannst du natürlich den Draw mit einem Mutex schützen.
Deine Draw() Code sieht auch richtig aus.
Kurzer hat geschrieben:Das heißt, dass beim Griff in die Struktur bei LockMutex() die Struktur noch nicht geschützt ist. Wenn ein zweiter thread gerade das gleiche versucht, wird es krachen.
Nein, genau dafür sind ja Mutex, selbst wenn 1000 Threads die selbe Procedure aufrufen, wird nur einer "der Erste" sein und die Prozedur sperren, die anderen 999 warten dann bis die der andere Thread Unlock() aufruft.
Selbst wenn du innerhalb eines Threads Draw() verschachtelt aufrufst, und ggf. zwei mal Lockst, musst du auch zwei mal wieder unlocken, was ja automatisch gegeben ist.

Edit: hier noch eine weiterführende Information: viewtopic.php?p=302892#p302892
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von Kurzer »

STARGÅTE hat geschrieben:Die Herangehensweise mit Create und Free ist verständlich, mache ich auch so.
Okay, dann werde ich das wieder so umbauen, dass kein Globaler Mutex verwendet wird, sondern einer pro instanziertem Gadget.
STARGÅTE hat geschrieben:Hier stellt sich mit die Frage wieso das passieren sollte? CustonGadgets können mit BindEvent alle Events abfangen und auch nur genau bei diesen Events sich neu zeichnen. Von "außen" also public sollte der Draw() befehl eigentlich nicht ausgeführt werden.
Okay, das war ein blödes Beispiel mit Draw().
Das Gadget, um das es geht, wird später eine bzw. mehrere Wellenformen bzw. mathematische Funktionen darstellen können. Quasi wie die Waveformanzeige bei einem Audioschnittprogramm.

Dafür gibt es dann öffentliche Funktionen, mit denen man dem Gadget einen darzustellenden Speicherblock mit einem Werte-Array zuweisen kann. Unter anderem kann man dann noch Farbe der Skalenbeschriftungen, Farbe der Wellenform und den Offset des Readpointers innerhalb des Speicherblocks setzen. Und erst, wenn man das alles gesetzt hat, dann kann man die Wellenform durch Aufruf von Update() anzeigen lassen. Es ist also Absicht, dass der User bestimmt, wann seine Wellenform im Gadget angezeigt wird.
STARGÅTE hat geschrieben:Wie auch immer kannst du natürlich den Draw mit einem Mutex schützen.
Deine Draw() Code sieht auch richtig aus.
Danke für die Bestätigung der richtigen Vorgehensweise. Ich werde mir das morgen, nach dem Rückbau auf Instanzenbasierte Mutexe (also einen Mutex pro Instanz statt eines globalen Mutex), dann noch einmal genauer untersuchen.
STARGÅTE hat geschrieben:
Kurzer hat geschrieben:Das heißt, dass beim Griff in die Struktur bei LockMutex() die Struktur noch nicht geschützt ist. Wenn ein zweiter thread gerade das gleiche versucht, wird es krachen.
Nein, genau dafür sind ja Mutex, selbst wenn 1000 Threads die selbe Procedure aufrufen, wird nur einer "der Erste" sein und die Prozedur sperren, die anderen 999 warten dann bis die der andere Thread Unlock() aufruft.
Selbst wenn du innerhalb eines Threads Draw() verschachtelt aufrufst, und ggf. zwei mal Lockst, musst du auch zwei mal wieder unlocken, was ja automatisch gegeben ist.
Prima, dann war mein erster Ansatz ja eigentlich völlig richtig. Der Fehler muss also woanders liegen. Ich schau da morgen mit frischem Kopf weiter nach.
STARGÅTE hat geschrieben:Edit: hier noch eine weiterführende Information: viewtopic.php?p=302892#p302892
Aha!!!

Freak schreibt dort:
Zitat:
If a thread calls LeaveCriticalSection when it does not have ownership of the specified critical section object, an error occurs that may cause another thread using EnterCriticalSection to wait indefinitely.
http://msdn.microsoft.com/en-us/library ... 85%29.aspx

Auf Deutsch: Ein Aufruf von UnlockMutex() aus dem falschen Thread kann dazu führen, dass ein anderer Thread bei LockMutex() hängenbleibt.
Ich ahne was mein Problem ist!
In meinem Testprogramm lasse ich vom Hauptthread zwei meiner Customgadgets erzeugen. Dann wird eines davon von einem Thread bedient und gleichzeitig von manuell von mir. Also aus der Hauptschleife bzw. der Ereignisschleife heraus.

Das heißt doch aber, dass die beiden Mutexe beide vom Hauptprogramm erzeugt worden sind. Keiner der Mutexe wurde vom thread erzeugt, der jetzt fleissig die Update() Prozedure des Gadgets befeuert.

Und da scheint jetzt genau das zu passieren, was Freak in seinem letzten Satz dort oben beschreibt.

Mist, wie löst man das denn? :freak:
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Useralter in 2024: 56 Jahre.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6999
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von STARGÅTE »

Wo der Mutex erstellt wird ist egal,
aber wenn Mutex#1 von Thread#1 gesperrt wird, darf nicht irgend ein anderer Thread genau diesen Mutex#1 wieder entsperren, sondern nur Thread#2
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
#NULL
Beiträge: 2235
Registriert: 20.04.2006 09:50

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von #NULL »

Kurzer hat geschrieben:Nun hatte ich es so realisiert, dass ich pro Instanz einen Mutex angelegt habe, der die internen Prozeduren gegen Mehrfachaufruf schützen soll. Die öffentliche Funktion Draw() könnte ja von mehren Threads gleichzeitig für die selbe Gadgetinstanz aufgerufen werden.

Das hat nichts gebracht, es kam dabei trotzdem zum deadlock (irgendwann).
Mit einem Mutex kannst du einen Deadlock nicht beheben oder verhindern, sondern lediglich überhaupt erst ermöglichen.

Bist du auch sicher, dass es ein Deadlock ist und nicht irgendeine Endlosschleife oder dergleichen?

Wenn ich mich richtig entsinne sind PB Mutexe reentrant, das heißt mehrfache Locks auf den selben Mutex im selben Thread werden 'durchgelassen' wenn der Lock bereits gesetzt ist. Das erfordert aber auch korrekte/balancierte Schachtellung von Lock/Unlock.
Einen Deadlock bekämst du z.B. wenn du nach einem Lock auf ein Signal von einem anderen Thread wartest, welcher seinerseits aber vor einem Lock auf den selben Mutex hängt und somit das Signal nie senden wird.
my pb stuff..
Bild..jedenfalls war das mal so.
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von Kurzer »

#NULL hat geschrieben:Mit einem Mutex kannst du einen Deadlock nicht beheben oder verhindern, sondern lediglich überhaupt erst ermöglichen.
Bist du auch sicher, dass es ein Deadlock ist und nicht irgendeine Endlosschleife oder dergleichen?
Nein, sicher bin ich mir nicht. Ich versuche das Ganze mal auf ein kleines Testprogramm einzudampfen und schaue, ob das Problem da auch auftritt. Auf jeden Fall brauche ich sowas wie einen Mutex, weil mir der Code sonst abkackt, wenn zwei Threads gleichzeitig StartDrawing() aufrufen.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Useralter in 2024: 56 Jahre.
Benutzeravatar
#NULL
Beiträge: 2235
Registriert: 20.04.2006 09:50

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von #NULL »

Ich habe mal folgendes verwendet um dergleichen zu debuggen:

Code: Alles auswählen

CompilerIf 0
  
  Procedure.s semaphoreName(s)
    Shared semaphoreMain, semaphoreDraw
    Select s
      Case semaphoreMain : ProcedureReturn "semaphoreMain"
      Case semaphoreDraw : ProcedureReturn "semaphoreDraw"
    EndSelect
  EndProcedure
  
  Procedure WaitSemaphore_proc(s)
    Debug "WaitSemaphore(" + semaphoreName(s) + ")"
    WaitSemaphore(s)
    Debug "WaitSemaphore(" + semaphoreName(s) + ") finished"
  EndProcedure
  
  Procedure SignalSemaphore_proc(s)
    Debug "SignalSemaphore(" + semaphoreName(s) + ")"
    SignalSemaphore(s)
    Debug "SignalSemaphore(" + semaphoreName(s) + ") finished"
  EndProcedure
  
  Macro WaitSemaphore(s)
    WaitSemaphore_proc(s)
  EndMacro
  
  Macro SignalSemaphore(s)
    SignalSemaphore_proc(s)
  EndMacro
CompilerEndIf
Nur so als Idee. Kann man ja auf Mutex umbauen.
semaphoreName() greift direkt auf die relevanten Variablen zu um den 'Namen' auszugeben, was in deinem Fall wahrscheinlich nicht so einfach ist, da du ja verschiedene pro Strukur/Element hast.
my pb stuff..
Bild..jedenfalls war das mal so.
Benutzeravatar
mk-soft
Beiträge: 3700
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von mk-soft »

Ich weiss nicht ob das CanvasGadget ein StartDrawing aus einen Thread unter Linux oder MacOS mag...
Änderungen an Gadget aus Threads funktionieren nicht unter Linux oder MacOS.
Daher gibt es ja mein Modul ThreadToGUI
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Antworten