Seite 1 von 6

Strings in Threads ohne "Threadsafe"

Verfasst: 12.11.2008 00:07
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?

Re: Thread crashed Programm an diversen Stellen

Verfasst: 12.11.2008 00:31
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

Verfasst: 12.11.2008 00:32
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).

Verfasst: 12.11.2008 00:43
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).

Verfasst: 12.11.2008 18:34
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?

Verfasst: 12.11.2008 18:55
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. :)

Verfasst: 12.11.2008 20:20
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.

Verfasst: 12.11.2008 20:28
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.

Verfasst: 12.11.2008 20:35
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.

Verfasst: 12.11.2008 21:05
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?