Deadlock-Problem

Für allgemeine Fragen zur Programmierung mit PureBasic.
SBond
Beiträge: 266
Registriert: 22.05.2013 20:35

Deadlock-Problem

Beitrag von SBond »

Hi Leute,

ich habe einige Probleme mit Threads und entstehenden Deadlocks.

Folgendes Beispiel:
Ich habe in meinem Fall zwei Prozeduren. Die erste Prozedur "Gadget_erstellen()" erstellt und löscht Gadgets. Die zweite Prozedur "Gadget_anpassen()" passt Gadgets an. "Gadget_erstellen()" wird immer im Hauptthread aufgerufen und "Gadget_anpassen()" immer in einem zweiten Thread aufgerufen. Würde man jetzt keinen Mutex verwenden, so kommt es schnell zu Fehlern/Abstürze, da der zweite Thread Gadgets anpassen will, die ggf. im Hauptthread gerade entfernt wurden.


Nun setze ich einen Mutex ein (siehe Code), der das gleichzeitige Ausführen beider Prozeduren verhindern soll.
....folgendes Passiert:


- zweiter Thread sperrt Mutex
- Hauptthread pausiert anschließend an der Zeile: LockMutex(iMutex) und wartet auf die Freigabe
- zweiter Thread verarbeitet während dessen die Prozedur und bleibt beim Befehl SetGadgetText() stehen
-Deadlock-

Ich denke mal, dass SetGadgetText() ggf. eine Nachricht sendet oder auf irgendetwas wartet (eventuell ein WindowEvent()). Dieses wird ja nicht mehr aufgerufen, da der Hauptthread gesperrt wurde. ....bin mir aber nicht sicher.


hier ist mal ein Beispielcode:

Code: Alles auswählen

Declare Gadget_erstellen()
Declare Gadget_anpassen()
Declare MeinThread(*Parameter)

Global iGadget.i
Global iTextGadget_Main.i
Global iTextGadget_Thread.i
Global iMutex.i = CreateMutex()



OpenWindow(0, 0, 0, 200, 100, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

iTextGadget_Main	 = TextGadget(#PB_Any, 10, 80, 80, 20, "Main")
iTextGadget_Thread	 = TextGadget(#PB_Any, 100, 80, 80, 20, "Thread")


CreateThread(@MeinThread(), 0)



Repeat
	
	If ElapsedMilliseconds() - Timer >= 1
		Timer = ElapsedMilliseconds()
		
				Counter_Main + 1
				SetGadgetText(iTextGadget_Main, "Main: " + Str(Counter_Main))
				
		Gadget_erstellen ()
	EndIf
	

Until WaitWindowEvent(0) = #PB_Event_CloseWindow




Procedure MeinThread(*Parameter)
	
	Protected Counter_Thread = 0
	
	Repeat	
		Counter_Thread + 1
		SetGadgetText(iTextGadget_Thread, "Thread: " + Str(Counter_Thread))
		Gadget_anpassen()	
	ForEver
	
EndProcedure



Procedure Gadget_erstellen ()
	
	LockMutex(iMutex)
		
		If IsGadget(iGadget)
			FreeGadget(iGadget)
		EndIf
		
		iGadget = ButtonGadget(#PB_Any, 50, 38, 100 ,20, "Button")
		
	UnlockMutex(iMutex)

EndProcedure



Procedure Gadget_anpassen ()
	
	If TryLockMutex(iMutex)
	
		If IsGadget(iGadget)
			SetGadgetText(iGadget, "irgendwas")
		EndIf
		
		UnlockMutex(iMutex)
	EndIf
	
EndProcedure

Wichtig wäre mir, dass die aufgerufene Prozedur im Hauptthread "Gadget_erstellen ()" erfolgreich verarbeitet und nie übersprungen wird. Würde ich also ein TryLockMutex im Hauptthread verwenden, dann wäre zwar ein Deadlock nicht mehr möglich, aber die Prozedur würde dann übersprungen werden, bis irgendwann einmal ein Lock möglich wäre.

Die Prozedur im zweiten Thread ist fast nebensächlich. Das bedeutet es wäre nicht schlimm, wenn diese mal übersprungen oder abgebrochen wird. Sollte diese Prozedur aufgerufen werden, so muss diese auch komplett verarbeitet oder abgebrochen werden. --> also wäre sowas wie ein PauseThread() ungünstig.

Ich denke mal killthread() kommt auch nicht in Frage, wenn ich mir die Hilfe so ansehe.


Hat jemand eine Idee?


viele Grüße
SBond
41 6c 73 6f 20 77 65 6e 6e 20 64 75 20 73 6f 20 76 69 65 6c 20 4c 61 6e 67 65 77 65 69 6c 65 20 68 61 73 74 2c 20 64 61 6e 6e 20 6b 61 6e 6e 73 74 20 64 75 20 61 75 63 68 20 67 6c 65 69 63 68 20 7a 75 20 6d 69 72 20 6b 6f 6d 6d 65 6e 20 75 6e 64 20 61 62 77 61 73 63 68 65 6e 2e

:D
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8837
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Deadlock-Problem

Beitrag von NicTheQuick »

Der Code ist für mich momentan etwas sinnfrei, sodass ich nicht ganz verstehe, worauf du eigentlich hinaus willst.

In der Hauptschleife zählst du so schnell es geht einen Zähler hoch und setzt ihn jedes Mal im ersten TextGadget neu. Danach erstellst du einen Button, und falls er schon existiert, löschst du ihn vorher nochmal, und das in jedem Durchlauf.

Im Thread zählst du ebenfalls einen Zähler hoch und setzt seinen Wert danach ebenfalls in eine TextGadget. Dann schaust du, ob der Button gerade existiert, was bis auf den allerersten Durchlauf immer der Fall sein wird, und versuchst den Button-Text zu ändern. Das dann wie du richtig erkannt hast nicht, weil die Events nicht abgearbeitet werden können.

Ich würde gerne genauer verstehen, was der tiefere Sinn von dem Code sein soll. Vielleicht gehst du es ja schon ganz falsch an.
SBond
Beiträge: 266
Registriert: 22.05.2013 20:35

Re: Deadlock-Problem

Beitrag von SBond »

ja, der Code an sich ist sinnfrei und soll nur das Problem des Deadlocks darstellen.
Diese Zähler dienen auch nur der "optischen Überzeugung", um zu beweisen, dass das Programm stehen bleibt.

Prinzipiell habe ich zwei verschiedene Prozeduren, wie von zwei verschiedenen Threads verarbeitet werden und niemals zeitgleich laufen dürfen. Mein Problem ist nun, dass SetGadgetText() oder ähnliche Funktionen gewisserweise pausieren und somit einen Deadlock erzeugen. Wie im Beispiel werden in einer Prozedur (Hauptthread) Gadgets erzeugt, gelöscht oder ersetzt und in der anderen werden diese Gadgets angepasst (Farbe, Text, Schriftart, ...)

Das Problem ist außerdem, dass die Prozedur im Hauptthread nicht übersprungen werden soll.
41 6c 73 6f 20 77 65 6e 6e 20 64 75 20 73 6f 20 76 69 65 6c 20 4c 61 6e 67 65 77 65 69 6c 65 20 68 61 73 74 2c 20 64 61 6e 6e 20 6b 61 6e 6e 73 74 20 64 75 20 61 75 63 68 20 67 6c 65 69 63 68 20 7a 75 20 6d 69 72 20 6b 6f 6d 6d 65 6e 20 75 6e 64 20 61 62 77 61 73 63 68 65 6e 2e

:D
Benutzeravatar
HeX0R
Beiträge: 3070
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3
Kontaktdaten:

Re: Deadlock-Problem

Beitrag von HeX0R »

SBond hat geschrieben:Prinzipiell habe ich zwei verschiedene Prozeduren, die von zwei verschiedenen Threads verarbeitet werden und niemals zeitgleich laufen dürfen.
Wieso benutzt Du Threads, wenn sie niemals zeitgleich laufen dürfen??

Du solltest wirklich ein Beispiel zusammenbasteln, bei dem man auch den Sinn dahinter versteht.
So wird Dir keiner helfen können.
Benutzeravatar
mk-soft
Beiträge: 3902
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Deadlock-Problem

Beitrag von mk-soft »

Gadgets anlegen geht nur einwandfrei aus dem Hauptprogramm wo das Fenster erstellt wurde.
Gadgets ändern (Text, Fabe, etc) gehen unter Windows auch aus Thread heraus. Linux schmiert ab...

Wie es gehen könnte...
http://www.purebasic.fr/english/viewtop ... t&start=15
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
SBond
Beiträge: 266
Registriert: 22.05.2013 20:35

Re: Deadlock-Problem

Beitrag von SBond »

danke erstmal für die Antworten :)

@HeX0R: ich denke du verstehst die Situation nicht. Ich kann dich eigentlich sogar verstehen, da ich es eventuell zu schwammig formuliert habe. ;)


Ich baue mir gerade eine Statusleiste die aus verschiedenen gadgets besteht. Die Gadgets können dabei dynamisch erstellt und gelöscht werden.
Die Inhalte (z.B. Text) der gadgets sollen außerdem periodisch aktualisiert werden. Wenn ich diese Aktualisierung in den Hauptthread packe, dann funktioniert es soweit.

Problem: beim Verschieben des Fensters, erfolgt keine Aktualisierung, da der Hauptthread pausiert.



Folgender Beispielcode:
-ein Gadget wird jede Sekunde durch ein anderes ersetzt
-die Aktualisierung erfolgt im Hauptthread
-der Wert für die Aktualisierung wird in einem anderem Thread erzeugt
-Problem: Aktualisierung pausiert beim verschieben des Fensters

Code: Alles auswählen

Declare Thread_A(*Parameter)

Global iGadget.i
Global iGadget_Typ.i
Global iGadget_Wert.i


OpenWindow(0, 0, 0, 200, 100, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CreateThread(@Thread_A(), 0)


Repeat
	
	; Gadget jede Sekunde auswechseln
	If ElapsedMilliseconds() - Timer >= 1000
		Timer = ElapsedMilliseconds()
		
		If IsGadget(iGadget): FreeGadget(iGadget): EndIf
		
		If iGadgetTyp = 0
			iGadget 	= ButtonGadget(#PB_Any, 50, 38, 100 ,20, "")
			iGadgetTyp 	= 1
			
		ElseIf iGadgetTyp = 1
			iGadget 	= CheckBoxGadget(#PB_Any, 50, 38, 100 ,20, "")
			iGadgetTyp 	= 0
		EndIf
		
	EndIf
	
	
	; Gadget anpasen
	SetGadgetText(iGadget, Str(iGadget_Wert))
	
	
Until WaitWindowEvent(10) = #PB_Event_CloseWindow



; Dieser Thread simuliert irgendeinen Vorgang, bei dem ständig ein Zustandswert (iGadget_Wert) generiert wird.
; ...das kann z.B. ein Packer sein oder eine lange Berechnung.
; In diesem Fall wird einfach nur ein Zähler hochgezählt
Procedure Thread_A (*Parameter)
	
	Repeat   
		iGadget_Wert + 1
		Delay (10)
	ForEver
	
EndProcedure

Um dieses Problem zu beheben, erfolgt die Aktualisierung einfach in einem anderen Thread. Das geht soweit Problemlos (siehe folgenden Beispielcode)

Code: Alles auswählen

Declare Thread_A(*Parameter)
Declare Thread_B(*Parameter)

Global iGadget.i
Global iGadget_Typ.i
Global iGadget_Wert.i


OpenWindow(0, 0, 0, 200, 100, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CreateThread(@Thread_A(), 0)
CreateThread(@Thread_B(), 0)

Repeat
	
	; Gadget jede Sekunde auswechseln
	If ElapsedMilliseconds() - Timer >= 1000
		Timer = ElapsedMilliseconds()
		
		If IsGadget(iGadget): FreeGadget(iGadget): EndIf
		
		If iGadgetTyp = 0
			iGadget 	= ButtonGadget(#PB_Any, 50, 38, 100 ,20, "")
			iGadgetTyp 	= 1
			
		ElseIf iGadgetTyp = 1
			iGadget 	= CheckBoxGadget(#PB_Any, 50, 38, 100 ,20, "")
			iGadgetTyp 	= 0
		EndIf
		
	EndIf
	
	
Until WaitWindowEvent(10) = #PB_Event_CloseWindow



; Dieser Thread simuliert irgendeinen Vorgang, bei dem ständig ein Zustandswert (iGadget_Wert) generiert wird
Procedure Thread_A (*Parameter)
	
	Repeat   
		iGadget_Wert + 1
		Delay (10)
	ForEver
	
EndProcedure



; Dieser Thread simuliert irgendeinen Vorgang, bei dem ständig ein Zustandswert (iGadget_Wert) generiert wird
Procedure Thread_B (*Parameter)
	
	Repeat   
		If IsGadget(iGadget)
			SetGadgetText(iGadget, Str(iGadget_Wert)) ; Gadget anpasen
		EndIf
		Delay (10)
	ForEver
	
EndProcedure

Das Problem, was jetzt hier entstehen kann, dass im Hauptthread gerade das Gadget gelöscht wird und in Thread_B() dieses gerade angepasst werden soll.
Folge: Crash

Man könnte nun im Thread mit IsGadget() die Gültigkeit prüfen, aber das klappt nicht zuverlässig in diesem Szenario. Denn auch hier kann IsGadget() ein OK geben und direkt danach wurde das Gadget schon gelöscht. ---> also nicht brauchbar bzw. nur bedingt

Nun kann man sagen: ok, der Thread_B() darf also nicht das Gadget aktualisieren, wenn zu diesem Zeitpunkt im Haupthread das Gadget ausgetauscht wird. Also nehmen wir einen Mutex um das zu verhindern. (siehe nächsten Code)

Code: Alles auswählen

Declare Thread_A(*Parameter)
Declare Thread_B(*Parameter)

Global iGadget.i
Global iGadget_Typ.i
Global iGadget_Wert.i
Global iMutex = CreateMutex()

OpenWindow(0, 0, 0, 200, 100, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CreateThread(@Thread_A(), 0)
CreateThread(@Thread_B(), 0)

Repeat
	
	; Gadget jede Sekunde auswechseln
	If ElapsedMilliseconds() - Timer >= 1000
		Timer = ElapsedMilliseconds()
		
		LockMutex(iMutex)
		
		If IsGadget(iGadget): FreeGadget(iGadget): EndIf
		
		If iGadgetTyp = 0
			iGadget 	= ButtonGadget(#PB_Any, 50, 38, 100 ,20, "")
			iGadgetTyp 	= 1
			
		ElseIf iGadgetTyp = 1
			iGadget 	= CheckBoxGadget(#PB_Any, 50, 38, 100 ,20, "")
			iGadgetTyp 	= 0
		EndIf
		
		UnlockMutex(iMutex)
		
	EndIf
	
	
Until WaitWindowEvent(10) = #PB_Event_CloseWindow



; Dieser Thread simuliert irgendeinen Vorgang, bei dem ständig ein Zustandswert (iGadget_Wert) generiert wird
Procedure Thread_A (*Parameter)
	
	Repeat   
		iGadget_Wert + 1
		Delay (10)
	ForEver
	
EndProcedure



; Dieser Thread simuliert irgendeinen Vorgang, bei dem ständig ein Zustandswert (iGadget_Wert) generiert wird
Procedure Thread_B (*Parameter)
	
	Repeat   
		LockMutex(iMutex)
		
		If IsGadget(iGadget)
			SetGadgetText(iGadget, Str(iGadget_Wert)) ; Gadget anpasen
		EndIf
		
		UnlockMutex(iMutex)
		Delay (10)
	ForEver
	
EndProcedure
Nun wird sichergestellt, dass das Gadget nicht zur gleichen Zeit getauscht und angepasst werden kann.
Aber nun kommt das nächste Problem:

-Thread B() sperrt gerade den Mutex
-Hauptthread will nun das Gadget tauschen und wartet auf die Mutexfreigabe
-Thread B() will das Gadget anpassen (mit SetGadgetText())
-Thread B() bleibt stehen, weil SetGadgetText() anscheinent ein Event senden möchte und noch auf irgendetwas wartet (ggf. WindowEvent())
-Thread B() kommt nicht soweit den Mutex freizugeben
-Deadlock
-GUI freeze

Nun kann man ja im Hauptthread einfach TryLockMutex() verwenden, notfalls den Code überspringen und im nächsten Zyklus versuchen den Mutex zu sperren. So kann der Deadlock verhindert werden.

---------------------------------------------------------------------


In meinen Programm sollte der Code niemals übersprungen werden, da andere Befehle von der erfolgreichen Verarbeitung des Codes abhängig waren. Ich habe es jetzt mit TryLockMutex() gelöst, auch wenn ich dieses gerne vermieden hätte (viele Codeänderungen). Da mein Code etwa 7000 Zeilen umfasst, wollte ich es auch hier nicht posten.


Aber trotzdem danke für eure Hilfe :)


viele Grüße
SBond
41 6c 73 6f 20 77 65 6e 6e 20 64 75 20 73 6f 20 76 69 65 6c 20 4c 61 6e 67 65 77 65 69 6c 65 20 68 61 73 74 2c 20 64 61 6e 6e 20 6b 61 6e 6e 73 74 20 64 75 20 61 75 63 68 20 67 6c 65 69 63 68 20 7a 75 20 6d 69 72 20 6b 6f 6d 6d 65 6e 20 75 6e 64 20 61 62 77 61 73 63 68 65 6e 2e

:D
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: Deadlock-Problem

Beitrag von ts-soft »

Da wir jetzt endlich das Problem kennen, hier die Lösung:
SBond hat geschrieben:Problem: beim Verschieben des Fensters, erfolgt keine Aktualisierung, da der Hauptthread pausiert.
Naja, BindEvent() wäre eine wesentlich sicherere und unkompliziertere Lösung :mrgreen: , ohne Threads, in denen man
wirklich keine Gadgets manipulieren sollte.
Sollte auch schneller sein, als mit Deinen Threads, aber wenns scheen macht <)

Gruß
Thomas
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
SBond
Beiträge: 266
Registriert: 22.05.2013 20:35

Re: Deadlock-Problem

Beitrag von SBond »

:shock:

schande über mich!! Daran habe ich ja gar nicht gedacht.
Würde ich mir bei jeder Dummheit einen Finger abschneiden, dann gäbe es morgen Fingersalat.


oh nein... jetzt kann ich alles wieder Rückgänging machen /:->

nochmals vielen Dank :mrgreen:
41 6c 73 6f 20 77 65 6e 6e 20 64 75 20 73 6f 20 76 69 65 6c 20 4c 61 6e 67 65 77 65 69 6c 65 20 68 61 73 74 2c 20 64 61 6e 6e 20 6b 61 6e 6e 73 74 20 64 75 20 61 75 63 68 20 67 6c 65 69 63 68 20 7a 75 20 6d 69 72 20 6b 6f 6d 6d 65 6e 20 75 6e 64 20 61 62 77 61 73 63 68 65 6e 2e

:D
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8837
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Deadlock-Problem

Beitrag von NicTheQuick »

Eine zusätzliche Idee wäre auch anstatt die Gadgets wild zu löschen und neu zu erstellen, einfach die Gadgets zu verstecken mit 'HideGadget()'. Dann kommt die Frage gar nicht auf, ob das Gadget existiert oder nicht, weil man es nämlich nie löschen muss. Die Variante mit dem Löschen und Neuerstellen finde ich sowieso ziemlich hässlich. Dauernd wird Speicher alloziert, Strukturen initialisiert, dann wieder freigegeben, vom Elternobjekt gelöscht. Das muss ja alles nicht sein, auch wenn es eh im Hintergrund passiert und einem so nicht auffällt.
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: Deadlock-Problem

Beitrag von ts-soft »

Naja, nächstes mal versuchen das Problem zu beschreiben und nicht so sehr Fragen warum der Code nicht geht,
dessen Sinn keiner versteht. :wink:

Also: genaue Problembeschreibung, also das was bezweckt ist und nicht was im Code falsch läuft.
Dann: ausführbaren, nicht funktionierenden Code.

Dann: ruckzuck Lösung bekommen, Bild machen und auf Facebook posten :mrgreen: , oder so ähnlich.

Schönen Sonntag noch,
Thomas
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Antworten