Mehrfaches Lock-/UnlockMutex im selben thread

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von mk-soft »

Habe ich mir gedacht...

Unter Windows geht es aus Thread direkt auf ein CanvasGadget zu zeichnen. Unter Linux auch, aber weiss nicht ob es zu einem Problem führen kann.

Unter MacOS kann man nicht aus einen Thread auf das CanvasGadget zeichnen.
Dieses funktioniert nur über dem Umweg mit einem PostEvent, damit das zeichnen im MainScope durchgeführt wird.

Code: Alles auswählen

;-TOP

Procedure Drawing1(Gadget)
  Protected.d dx, dy, angle, center_x, center_y, delta_y
  If IsGadget(Gadget) And GadgetType(Gadget) = #PB_GadgetType_Canvas
    dx = GadgetWidth(Gadget)
    dy = GadgetHeight(gadget)
    angle = GetGadgetData(Gadget)
    
    If StartVectorDrawing(CanvasVectorOutput(Gadget))
      ;Debug "Draw"
      ; Hintergrund
      AddPathBox(0.0, 0.0, dx, dy)
      VectorSourceColor(#Yellow | $FF000000)
      FillPath()
      
      ; Rahmen
      AddPathBox(0.0, 0.0, dx, dy)
      VectorSourceColor(#Gray | $FF000000)
      StrokePath(4.0)
      
      center_x = dx * 0.5
      center_y = dy * 0.5
      
      If dy > dx
        delta_y = dx * 0.9
      Else
        delta_y = dy * 0.9
      EndIf
      
      ResetCoordinates()
      RotateCoordinates(center_x, center_y, angle)
      AddPathCircle(center_x, delta_y, 10.0)
      VectorSourceColor(#Red | $FF000000)
      FillPath()
      
      angle + 15.0
      SetGadgetData(Gadget, angle)
      StopVectorDrawing()
    EndIf
  EndIf
EndProcedure

Procedure evDrawing1()
  Drawing1(EventGadget())
EndProcedure

Procedure thDrawing1(Gadget)
  Repeat
    Delay(100)
    If IsGadget(Gadget)
      PostEvent(#PB_Event_Gadget, 0, Gadget, #PB_EventType_Change)
      ;Drawing1(Gadget)
    Else
      Break
    EndIf
  ForEver
EndProcedure

Procedure thDrawing2(Gadget)
  Repeat
    Delay(100)
    If IsGadget(Gadget)
      PostEvent(#PB_Event_Gadget, 0, Gadget, #PB_EventType_Change)
      ;Drawing1(Gadget)
    Else
      Break
    EndIf
  ForEver
EndProcedure


If OpenWindow(0, 0, 0, 440, 220, "CanvasGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(0, 10, 10, 200, 200)
  CanvasGadget(1, 230, 10, 200, 200)
  
  BindGadgetEvent(0, @evDrawing1(), #PB_EventType_Change)
  BindGadgetEvent(1, @evDrawing1(), #PB_EventType_Change)
  
  CreateThread(@thDrawing1(), 0)
  ;Delay(50)
  CreateThread(@thDrawing2(), 1)
  
  Repeat
    Event = WaitWindowEvent()
    
  Until Event = #PB_Event_CloseWindow
EndIf
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von mk-soft »

Update

Nächstes Problem...
Zeichnen ohne Option ThreadSafe führt es zum Crash.

Auch wenn ThreadSafe aktiviert wurde und direkt aus dem Thread gezeichnet wird, führt es stocken des zeichnen.

Resume:
Also meiner Meinung nach ist es immer besser über PostEvent das neu Zeichnen des Gadget anzustossen.

P.S.
Um ein Blocken des MainScope (WaitWindowEvent) zu vermeiden, ist es zu empfehlen mit einer Kopie der Daten für das Gadget mit PostEvent zu senden. (EventData)
Somit kann man immer noch mit LockMutex die Daten vor Änderungen aus verschiedenen Thread schützen.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
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 »

Das Problem ist irgendwie was anderes und hat nichts mit dem Thread oder LockMutex zu tun (zumindest nicht auf Windows, da man da ja aus einem Thread heraus die Drawingbefehle aufrufen kann).

Ich habe mein Customgadget und den Testcode dazu mal eingedampft aufs Wesentliche und dachte, dass ich das Blockingproblem damit nachstellen kann. Das kann ich aber leider nicht. Das einzige was ich damit nachstellen kann, ist ein Speicherzugriffsfehler, wenn man vergisst das Programm ohne die Compilereinstellung "Threadsafe" laufen zu lassen - siehe folgendes GIF.

Was für mich die Frage aufwirft: Sind die MutexBefehle ohne die Compileroption "Threadsafe" wirkungslos? Weil, eigentlich habe ich die Drawingbefehle hier in dem Test komplett mit einem Mutex gekapselt.

Bild

Ich weiß nicht, ob das posten dieses Testcodes hier sinnvoll ist, da er immer noch 190 Zeilen umfasst.

Meine Vermutung geht hier in eine andere Richtung, die ich jetzt untersuche. Evtl. liegt die Ursache am zu frühen "deattachen" (lösen) eines Speicherblocks mit darzustellenden Wellenformdaten vom Gadget während diese Daten noch gezeichnet werden (bzw. derzeit wird nur die Skala gezeichnet ohne die eigentliche Wellenform).

Hier mal das Blocking im echten Gadget als Animation:

Bild

Ich muss das Verhalten noch weiter untersuchen, da es wechselhaft ist. Gestern trat das Blocking z.B. auch dann auf, wenn ich im ersten Gadget (oben) mit dem "->" Button manuell gescrollt habe und der Thread unten die Skala im zweiten Gadget gescrollt hat. Also waren hier völlig unterschiedliche Gadgets, Strukturen und Speicherblöcke involviert, die bis auf die gemeinsam genutzten Prozeduren zur "Datenverarbeitung eigentlich nichts miteinander zu tun haben.

In dem GIF da oben von heute ist das Programm abgeschmiert, als ich dem Thread (unteres Gadget) die Wellenform-Speicherblöcke weggeschaltet habe (die Wellenform selbst wird noch nicht gezeichnet, nur die zugehörige Skala). Daher mein Verdacht, dass es da noch ein Problem gibt (wobei das schon mal hervorragend funktioniert hat).

Wer Interesse hat sich den Code anzusehen... ich habe nichts dagegen den bisherigen Code hier zu posten bzw. als zip-file zu hinterlegen.
"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
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von NicTheQuick »

Du brauchst Threadsafe eigentlich nur für Strings, soweit ich das weiß. Und da du jede Menge Debug-Ausgaben tätigst, sollte es an sein. Für alles, was nichts mit Strings zu tun hat, ist soweit ich weiß kein Threadsafe notwendig.
Bild
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von mk-soft »

Das problem ist das man wohl in deinen fall die ohne Threadsafe die Drawing befehle durcheinander kommen...
Mit ThreadSafe wird wohl komplett von PB aus zwischen StartDrawing und StopDrawing ein internes Mutex ausgeführt.

Ohne Option ThreadSafe must du selber um StartDrawing und StopDrawing ein Globales Mutex verwenden.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
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 »

Aha, also doch ein globales Mutex und nicht eines pro Gadget-Instanz.
Das probiere ich mit dem Testcode gleich mal aus. Derzeit hat dort jede Gadgetinstanz einen Mutex.

@Stargate: Das man threadsafe nur für Stringnutzng benötigt hatte ich auch noch so im Hinterkopf.
Macht es einen Unterschied, ob ich Debug nutze oder ein PrintN in der Console? Weil: Das Beispiel aus der Hilfe zum Mutex schmiert *nicht* ab, wenn man keine "threadsafe" Option enabled hat.

Code: Alles auswählen

  ; Starten Sie diesen Code so wie er ist. Sie werden sehen, das die Zeilen
  ; gemischt durch die Threads ausgegeben werden. Jetzt entfernen Sie den
  ; Kommentar vor den Mutex-Befehlen und die Strings werden in Reihenfolge
  ; ausgegeben, da nur ein Thread zur gleichen Zeit das Recht zum Ausführen
  ; der Print-Befehle bekommt.
  ;
  Procedure WithoutMutex(*Number)     
    Shared Mutex
    
    For a = 1 To 5      
      ;LockMutex(Mutex)    ; entfernen Sie das ';' um den Unterschied zu sehen
    
      PrintN("Thread "+Str(*Number)+": Trying to print 5x in a row:")
      For b = 1 To 5
        Delay(50)
        PrintN("Thread "+Str(*Number)+" Line "+Str(b))
      Next b          
      
      ;UnlockMutex(Mutex) ; entfernen Sie das ';' um den Unterschied zu sehen
    Next a    
  EndProcedure
  
  OpenConsole()
  Mutex = CreateMutex()
  
  thread1 = CreateThread(@WithoutMutex(), 1)
  Delay(25)
  thread2 = CreateThread(@WithoutMutex(), 2)
  Delay(25)
  thread3 = CreateThread(@WithoutMutex(), 3)
  
  WaitThread(thread1)
  WaitThread(thread2)
  WaitThread(thread3)
  
  Input()
Immerhin weiß ich jetzt, dass ein Mutex auch ohne die "Threadsafe" Option funktioniert.

Nachtrag: Okay, also mit dem globalen Mutex im Testcode läuft dieser einwandfrei durch, auch ohne "Threadsafe" Option in den Compilereinstellungen. Zumindest ist der Code nach 1 Minute manuellem Dauerklicken noch nicht abgeschmiert.
"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
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von mk-soft »

NicTheQuick hat geschrieben:Du brauchst Threadsafe eigentlich nur für Strings, soweit ich das weiß. Und da du jede Menge Debug-Ausgaben tätigst, sollte es an sein. Für alles, was nichts mit Strings zu tun hat, ist soweit ich weiß kein Threadsafe notwendig.
Nicht ganz...

Ohne ThreadSafe crashen die Drawing-Befehle.
Siehe mein Beispiel und ändere es auf Thread Drawing...

Code: Alles auswählen

Procedure thDrawing1(Gadget)
  Repeat
    Delay(50)
    If IsGadget(Gadget)
      ;PostEvent(#PB_Event_Gadget, 0, Gadget, #PB_EventType_Change)
      Drawing1(Gadget)
    Else
      Break
    EndIf
  ForEver
EndProcedure

Procedure thDrawing2(Gadget)
  Repeat
    Delay(50)
    If IsGadget(Gadget)
      ;PostEvent(#PB_Event_Gadget, 0, Gadget, #PB_EventType_Change)
      Drawing1(Gadget)
    Else
      Break
    EndIf
  ForEver
EndProcedure
@Kurzer
Ohne PostEvent zum neu zeichnen wird Dein Gadget nicht unter MacOS Funktionieren.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
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 »

Abgesehen vom MacOS....

Wenn Du die Drawing-Section mit einem Mutex schützt, dann läuft das auch ohne "Threadsafe" direkt aus den threads heraus - also hier bei mir zumindest. Da crasht nichts.

Edit2: Rot = Ich bin schon völlig matschig. Genau das hattest Du ja selbst schon oben geschrieben. :freak:
Ohne Option ThreadSafe must du selber um StartDrawing und StopDrawing ein Globales Mutex verwenden.
Ich geh erstmal ins Bett und mache morgen weiter. :lol:

Code: Alles auswählen

EnableExplicit

Global.i Event, iMutex = CreateMutex()

Procedure Drawing1(Gadget)
  Protected.d dx, dy, angle, center_x, center_y, delta_y
  LockMutex(iMutex)
  
  If IsGadget(Gadget) And GadgetType(Gadget) = #PB_GadgetType_Canvas
    dx = GadgetWidth(Gadget)
    dy = GadgetHeight(gadget)
    angle = GetGadgetData(Gadget)
    
    If StartVectorDrawing(CanvasVectorOutput(Gadget))
      ;Debug "Draw"
      ; Hintergrund
      AddPathBox(0.0, 0.0, dx, dy)
      VectorSourceColor(#Yellow | $FF000000)
      FillPath()
      
      ; Rahmen
      AddPathBox(0.0, 0.0, dx, dy)
      VectorSourceColor(#Gray | $FF000000)
      StrokePath(4.0)
      
      center_x = dx * 0.5
      center_y = dy * 0.5
      
      If dy > dx
        delta_y = dx * 0.9
      Else
        delta_y = dy * 0.9
      EndIf
      
      ResetCoordinates()
      RotateCoordinates(center_x, center_y, angle)
      AddPathCircle(center_x, delta_y, 10.0)
      VectorSourceColor(#Red | $FF000000)
      FillPath()
      
      angle + 15.0
      SetGadgetData(Gadget, angle)
      StopVectorDrawing()
    EndIf
  EndIf
  UnlockMutex(iMutex)

EndProcedure

Procedure evDrawing1()
  Drawing1(EventGadget())
EndProcedure

Procedure thDrawing1(Gadget)
  Repeat
    Delay(100)
    If IsGadget(Gadget)
      ;PostEvent(#PB_Event_Gadget, 0, Gadget, #PB_EventType_Change)
      Drawing1(Gadget)
    Else
      Break
    EndIf
  ForEver
EndProcedure

Procedure thDrawing2(Gadget)
  Repeat
    Delay(100)
    If IsGadget(Gadget)
      ;PostEvent(#PB_Event_Gadget, 0, Gadget, #PB_EventType_Change)
      Drawing1(Gadget)
    Else
      Break
    EndIf
  ForEver
EndProcedure


If OpenWindow(0, 0, 0, 440, 220, "CanvasGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(0, 10, 10, 200, 200)
  CanvasGadget(1, 230, 10, 200, 200)
  
;   BindGadgetEvent(0, @evDrawing1(), #PB_EventType_Change)
;   BindGadgetEvent(1, @evDrawing1(), #PB_EventType_Change)
  
  CreateThread(@thDrawing1(), 0)
  ;Delay(50)
  CreateThread(@thDrawing2(), 1)
  
  Repeat
    Event = WaitWindowEvent()
    
  Until Event = #PB_Event_CloseWindow
EndIf
Und so habe ich es auch in meinem Gadget gemacht (ich muss es halt wieder auf einen globalen Mutex umbauen, aber in meinem Testcode habe ich das schon erfolgreich getestet)

Edit1: Wenn das Gadget fertig ist, liegt es ja am User wie er es einsetzt (also in einem Thread oder nicht). Das Gadget selbst nutzt/benötigt keinen Thread. Da ich momentan unter Win7 unterwegs bin, habe ich dieses feature einfach zum testen genutzt. Ich vermute wie gesagt, dass das Blocking eine andere Ursache hat. Ich bau das erstmal wieder auf einen globalen Mutex um. Und wenn ich einen InstanceCounter führe, dann kann ich den Mutex nach dem Freigeben der letzten Gadgetinstanz auch wieder regulär freigeben (das war mir gestern noch nicht so ganz klar, wie ich das regeln kann).
"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
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Re: Mehrfaches Lock-/UnlockMutex im selben thread

Beitrag von Kurzer »

So, nochmal besten Dank an euch. :allright:

Ich habe heute einige Teile neu geschrieben. Letztendlich reicht es den Drawing-Teil mit einem einzigen globalen Mutex zu schützen - also über alle Gadgetinstanzen hinweg nur ein Mutex.

Ich erzeuge ihn in der Create() Prozedur beim Erstellen der ersten Gadgetinstanz...

Code: Alles auswählen

		; Create the global Mutex at the very first call
		If iMutexCounter = 0
			iGlobalMutex = CreateMutex()
			If iGlobalMutex = 0 : ProcedureReturn #False : EndIf
			iMutexCounter + 1
			Debug "Mutex created"
		EndIf
... und gebe ihn beim Entfernen der letzten Gadgetinstanz innerhalb der Free() Prozedur wieder frei.

Code: Alles auswählen

		; Free the global Mutex at the very last call of Free
		If iMutexCounter = 0
			FreeMutex(iGlobalMutex)
			Debug "Mutex freed"
		EndIf
		iMutexCounter - 1
Mein Testprogramm läuft damit -zumindest mit gesetzter threadsafe Compileroption- stabil.

Das einzige Problem war noch, dass mir der laufende Thread beim Schließen des Fensters eingefroren ist, obwohl ich ihn mittels globaler Variable zum Beenden aufgefordert habe.

Ursache war die Tatsache, dass mein Customgadget (ein Container Canvas) selbst noch 5 reguläre PB Gadgets enthält.
Nach dem Klick auf den Schließen-Button des Fensters wird dann die Eventschleife verlassen. Dummerweise ist der Thread unter Umständen dann noch damit beschäftigt dem Scrollbar-Gadget in meinem Gadget eine neue Position setzen. Das führt offenbar dazu, dass das Scrollbar-Gadget ein Event abfeuert und wenn dieser von der Eventschleife nicht abgeholt wird, der Thread aus der "ScrollBar-Position setzen" Prozedur nicht mehr wieder kommt.

Das hat ein bisschen gedauert bis ich da drauf gekommen bin. Abhilfe schafft hier ein WaitWindowEvent(1) innerhalb der Schleife die auf das Beenden des Threads wartet.

Code: Alles auswählen

	; Drawingthread starten
	stDrawingThread\iThread = CreateThread(@DrawingThread(), 0)
	If stDrawingThread\iThread
		
		Repeat
			iEvent = WaitWindowEvent()
		Until iEvent = #PB_Event_CloseWindow
		
		; Drawingthread beenden
		stDrawingThread\iQuit = #True 
		Repeat
			WaitWindowEvent(1)
		Until IsThread(stDrawingThread\iThread) = 0
		Debug "Thread closed"
	EndIf 
Jetzt kann ich endlich weiter an den eigentlichen Prozeduren des Gadgets schreiben. Juhu. :allright:
"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.
Antworten