Strings in Threads ohne "Threadsafe"

Anfängerfragen zum Programmieren mit PureBasic.
Benutzeravatar
Fluid Byte
Beiträge: 3110
Registriert: 27.09.2006 22:06
Wohnort: Berlin, Mitte

Strings in Threads ohne "Threadsafe"

Beitrag von Fluid Byte »

Ich habe mit Threads bisher noch nicht wirklich viel gemacht weiß aber das eine Menge Probleme mit sich bringen können. Der Zweck meines Programms soll sein eine Datenbank im Hintergrund auszulesen. Ich hab' was zusammengestrick allerdings ist es Glückssache ob es abschmiert oder nicht.

Code: Alles auswählen

EnableExplicit

Declare DataAccessProc(lParam)
Define Highscore

Define i, DataAccessing, ThreadID

Procedure.s MyFunction(Wert1,Wert2)
	; anderer Kram
EndProcedure

For i=1 To 9999
	DataAccessing = 1						
	If IsThread(ThreadID) : KillThread(ThreadID) : EndIf
	ThreadID = CreateThread(@DataAccessProc(),@DataAccessing)
Next

Procedure DataAccessProc(lParam)
	Protected HighScore$ = MyFunction(1,20)
	
  	PokeL(lParam,0)
EndProcedure
Einfach den Code ein paar mal hintereinander ausführen, wird irgendwann abschmieren.

Die Frage ist also was mach ich verkehrt?
Zuletzt geändert von Fluid Byte am 12.11.2008 19:02, insgesamt 1-mal geändert.
Windows 10 Pro, 64-Bit / Outtakes | Derek
Benutzeravatar
Kiffi
Beiträge: 10711
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Re: Thread crashed Programm an diversen Stellen

Beitrag von Kiffi »

Fluid Byte hat geschrieben:Die Frage ist also was mach ich verkehrt?
KillThread() sollte nach Möglichkeit nur in absoluten Notfällen eingesetzt
werden. Weitere Infos in der PB-Hilfe.

Grüße ... Kiffi
a²+b²=mc²
Benutzeravatar
HeX0R
Beiträge: 3040
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:

Beitrag von HeX0R »

Da du einen String in deiner Threadprozedur benutzt, solltest du Threadsave aktivieren.

Ausserdem ein besseres Beispiel machen ;)
Den Thread sofort wieder zu killen, wenn er gerade mal so gestartet ist, ist nicht wirklich gut.

Im Prinzip, sollte KillThread eh nur in Ausnahmefällen benutzt werden, ein Thread lässt sich auch vernünftig beenden (schon klar, dass das nur ein zusammengepfriemeltes Beispiel ist).
Benutzeravatar
HeX0R
Beiträge: 3040
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:

Beitrag von HeX0R »

Hier mal ein Beispiel, wie man einen Thread normalerweise beenden sollte und wie man KillThread benutzt:

Code: Alles auswählen

Structure THREADVALUE
	ThreadID.i
	StopIt.i
	Bla.l
	Blubb.s
EndStructure


Procedure MyThread(*TV.THREADVALUE)
	;
	Repeat
		Delay(5)
		If *TV\StopIt
			Break
		EndIf
	ForEver
	
	*TV\ThreadID = #False
EndProcedure


Define.THREADVALUE T

T\ThreadID = CreateThread(@MyThread(), @T)
OpenWindow(0, 0, 0, 100, 100, "", $C8<<16)
While WaitWindowEvent() ! 16 : Wend
;Now Close Thread
T\StopIt = #True
If T\ThreadID And WaitThread(T\ThreadID, 500) = 0
	;Ok, this thread seems to be dead (endless loop?)
	Debug "killed ?"
	KillThread(T\ThreadID)
EndIf
Wie man sieht, ist KillThread nur noch die Notbremse, falls der Thread in der Endlosschleife feststeckt (was ja letztendlich dann auch an einem selbst liegt).
Benutzeravatar
Fluid Byte
Beiträge: 3110
Registriert: 27.09.2006 22:06
Wohnort: Berlin, Mitte

Beitrag von Fluid Byte »

HeX0R hat geschrieben:Da du einen String in deiner Threadprozedur benutzt, solltest du Threadsave aktivieren.
Gerade das möchte ich vermeiden. Mit Threadsafe aktiviert funktioniert zwar mein obiges Snippet aber geht auf Kosten der Geschwindigkeit.

Ist diese Compilereinstellung zwingend notwending wenn ich Strings verwende?
Windows 10 Pro, 64-Bit / Outtakes | Derek
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Beitrag von Thorium »

Fluid Byte hat geschrieben:
HeX0R hat geschrieben:Da du einen String in deiner Threadprozedur benutzt, solltest du Threadsave aktivieren.
Gerade das möchte ich vermeiden. Mit Threadsafe aktiviert funktioniert zwar mein obiges Snippet aber geht auf Kosten der Geschwindigkeit.

Ist diese Compilereinstellung zwingend notwending wenn ich Strings verwende?
Nicht zwingend, ist aber zu empfehlen. Du kannst den String auch selbst schützen. Du kannst dafür einen Mutex verwenden.

Beispiel aus der PB-Hilfe:

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()
Das selbst schützen scheint aber gerade bei Strings nicht immer wasserdicht zu sein. Hatte in meinem asyncronen Ressourceloader auch alles schön mit Mutexen (keine Ahnung was die Mehrzahl ist) gesichert. Trotzdem musste ich Threadsave aktivieren, da es Crashes gab. Zwar sehr selten aber Crash ist Crash und kann nicht tolleriert werden. :)
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
Fluid Byte
Beiträge: 3110
Registriert: 27.09.2006 22:06
Wohnort: Berlin, Mitte

Beitrag von Fluid Byte »

Thorium hat geschrieben:Du kannst den String auch selbst schützen. Du kannst dafür einen Mutex verwenden.
Ich habe in meinem obigen Code mal für Testzwecke

Code: Alles auswählen

For i=1 To 9999
	DataAccessing = 1			
	If IsThread(ThreadID) : KillThread(ThreadID) : EndIf
	ThreadID = CreateThread(@DataAccessProc(),@DataAccessing)
Next
durch

Code: Alles auswählen

For i=1 To 9999
	DataAccessing = 1			
	If IsThread(ThreadID) = 0
		ThreadID = CreateThread(@DataAccessProc(),@DataAccessing)
	EndIf
Next
ersetzt und es funktioniert. Das klappt aber nur in diesem Democode und nicht in meinem eigentlichen Programm. Und ich weiß auch warum. Folgt nach der Erstellung des Threads noch weiterer Programmcode schmiert es ab, auch mit Mutexes.

Code: Alles auswählen

EnableExplicit

Global Mutex = CreateMutex() 

Declare DataAccessProc(lParam)
Define Highscore

Define i, DataAccessing, ThreadID, TempDir$

Procedure.s MyFunction(Wert1,Wert2)
	; anderer Kram
EndProcedure

For i=1 To 9999
	DataAccessing = 1			
	If IsThread(ThreadID) = 0
		ThreadID = CreateThread(@DataAccessProc(),@DataAccessing)
		TempDir$ = GetTemporaryDirectory()
	EndIf
Next

Procedure DataAccessProc(lParam)
	LockMutex(Mutex) 
	
	Protected HighScore$ = MyFunction(1,20)
	
  	PokeL(lParam,0)
  	
  	UnlockMutex(Mutex)
EndProcedure
Thorium hat geschrieben:Das selbst schützen scheint aber gerade bei Strings nicht immer wasserdicht zu sein.
Ich denke da scheint der Hund begraben zu sein. Ich erinnere mich noch an meinen Mediaplayer der im Hintergrund die MP3 Tags auslesen sollte. Hatte da dieselben Probleme. Habe auch versucht den Code aufs Wesentliche zu reduzieren und Mutexes zu benutzen aber vergebens. Nur mit Threadsafe funktioniert die Anwendung einwandfrei.
Zuletzt geändert von Fluid Byte am 12.11.2008 20:32, insgesamt 1-mal geändert.
Windows 10 Pro, 64-Bit / Outtakes | Derek
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Beitrag von Thorium »

Fluid Byte hat geschrieben:

Code: Alles auswählen

EnableExplicit

Global Mutex = CreateMutex() 

Declare DataAccessProc(lParam)
Define Highscore

Define i, DataAccessing, ThreadID, TempDir$

Procedure.s MyFunction(Wert1,Wert2)
	; anderer Kram
EndProcedure

For i=1 To 9999
	DataAccessing = 1			
	If IsThread(ThreadID) = 0
		ThreadID = CreateThread(@DataAccessProc(),@DataAccessing)
		TempDir$ = GetTemporaryDirectory()
	EndIf
Next

Procedure DataAccessProc(lParam)
	LockMutex(Mutex) 
	
	Protected HighScore$ = MyFunction(1,20)
	
  	PokeL(lParam,0)
  	
  	UnlockMutex(Mutex)
EndProcedure
Das der Mutex da nix bringt ist klar. Da du ja nur von einer Stelle aus auf die Highscore zugreifst. Ein Mutex bringt nur was, wenn mehrere Codestellen auf die gleiche Ressource (z.b. String) zugreifen. Dann kannst du beide mittels Mutex dazu bewegen aufeinander zu warten anstatt direkt zuzugreifen. Wenn du nur in dem einen Thread auf die Highscore zugreifst und sonst nirgends, wirst du um Threadsave nicht drummherum kommen. Dann greift PB intern auf irgendwas mehrfach gleichzeitig zu.
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Beitrag von Thorium »

Mir fällt grad auf das der Hund natürlich auch in:
Procedure.s MyFunction(Wert1,Wert2)
; anderer Kram
EndProcedure
begraben sein kann.

Jenachdem was da gemacht wird in MyFunction und auf was zugegriffen wird. Du hast ja praktisch nur den Aufruf von MyFunction gesichert und das auch nur in dem Highscore-Thread / den Highscore-Threads.
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
Fluid Byte
Beiträge: 3110
Registriert: 27.09.2006 22:06
Wohnort: Berlin, Mitte

Beitrag von Fluid Byte »

Gut möglich aber ich würde gerne erstmal den Democode zum laufen bringen.
Außerdem hab' ich vergessen zu erwähnen das wenn man die Zeile

Code: Alles auswählen

TempDir$ = GetTemporaryDirectory()
auskommentiert funktioniert der Code wieder einwandfrei. Die Variable 'TempDir$' wird außerhalb des Threads deklariert und gesetzt. Warum schmiert das Programm dann aber trotzdem ab?
Windows 10 Pro, 64-Bit / Outtakes | Derek
Antworten