Seite 1 von 2

Deadlock-Problem

Verfasst: 15.03.2014 15:43
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

Re: Deadlock-Problem

Verfasst: 15.03.2014 16:41
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.

Re: Deadlock-Problem

Verfasst: 15.03.2014 17:08
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.

Re: Deadlock-Problem

Verfasst: 16.03.2014 00:25
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.

Re: Deadlock-Problem

Verfasst: 16.03.2014 06:07
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

Re: Deadlock-Problem

Verfasst: 16.03.2014 14:21
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

Re: Deadlock-Problem

Verfasst: 16.03.2014 15:26
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

Re: Deadlock-Problem

Verfasst: 16.03.2014 15:41
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:

Re: Deadlock-Problem

Verfasst: 16.03.2014 15:49
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.

Re: Deadlock-Problem

Verfasst: 16.03.2014 15:53
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