Thread ID Problem auf dem MAC

Anfängerfragen zum Programmieren mit PureBasic.
ChristianH
Beiträge: 8
Registriert: 21.06.2020 20:15

Thread ID Problem auf dem MAC

Beitrag von ChristianH »

Hi,

aufgrund eines anderen Themas versuche ich mich gerade in das Thema Thread bei PB einzuarbeiten. Aber irgendwie komme ich gerade nicht weiter
oder verstehe das irgendwie falsch. Hat nicht jeder neue Thread eine neue ID? Ich dachte diese Thread ID s würden zufällig vergeben und bei einem neuen Thread eine neue. Um es aber an einem Beispiel zu erklären folgender Code.

Gruß Christian

Code: Alles auswählen

Threaded Tcontrol.i
Threaded Ti.i

Structure Person
    Name$
    Age.b
    ThreadID.i
  EndStructure
  
 
CompilerIf Not #PB_Compiler_Debugger
CompilerError "Please enable the debugger!"
CompilerEndIf


 
  
 
Procedure Thread(*Parameters.Person)
    Ti.i = 1
    Tcontrol.i = 0
    While Tcontrol.i = 0     
    
    Debug *Parameters\Name$
    Debug *Parameters\Age
    Debug *Parameters\ThreadID
    
    Ti.i = Ti.i + 1  
        
          If Ti.i > 2
            Tcontrol.i = 1
          EndIf
     Wend 
    
    KillThread(*Parameters\ThreadID.i) 
    
  EndProcedure
  

  *Parameters.Person = AllocateMemory(SizeOf(Person))
  *Parameters\Name$ = "Peter"
  *Parameters\Age   = 22
  *Parameters\ThreadID = CreateThread(@Thread(), *Parameters) ; Einen Zeiger auf unsere Struktur an den Thread senden
   Delay(1000)  
  
  
  *Parameters.Person = AllocateMemory(SizeOf(Person))
  *Parameters\Name$ = "Ulf"
  *Parameters\Age   = 33
  *Parameters\ThreadID = CreateThread(@Thread(), *Parameters) ; Einen Zeiger auf unsere Struktur an den Thread senden
   Delay(1000)  
    
  FreeMemory(*Parameters)
  ClearStructure(*Parameters, Person)

Hier die Debug Ausgabe von dem Code
Peter
22
123145534771200
Peter
22
123145534771200
Ulf
33
123145534771200
Ulf
33
123145534771200
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
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: Thread ID Problem auf dem MAC

Beitrag von NicTheQuick »

Das KillThread() ist schon mal falsch da. Diese Funktion sollte man am besten nie benutzen. Und schon gar nicht von innerhalb des Threads selbst. Wenn sich der Thread beenden soll, dann kann er ja einfach ProcedureReturn aufrufen oder sein Ende erreichen und dann ist er auch aus.

Die zweite Sache ist, dass der Thread schon starten kann, bevor seine ID in *Parameters\ThreadID gespeichert ist. Das heißt nach deinem Codebeispiel weiß er vielleicht noch gar nicht welche ThreadID er hat, wenn er versucht darauf zuzugreifen. Sobald du CreateThread aufrufst, beginnt ein nebenläufiger Thread. Du weißt nicht, ob dein Hauptprogramm zuerst weiter macht oder der Thread, oder ob beide sogar echt parallel laufen. Möchtest du einen geregelten Ablauf in bestimmten Situationen, musst du mit Mutexen und Semaphoren arbeiten.

Im Mai hab ich im englischen Forum eine Möglichkeit gepostet wie ein Thread sicher an seine eigene ID kommen kann: Get own threadID without passing as parameter

Dann noch zu deiner Frage bezüglich gleiche Thread-IDs: Verschiedene Threads haben natürlich eine unterschiedliche ID, aber Thread-IDs können wiederverwendet werden, wenn der entsprechende Thread nicht mehr existiert. Zufällig werden sie auch nicht vergeben. Jedes Betriebssystem hat da seine eigene Art wie es solche IDs vergibt. Manche IDs sind vielleicht einfach nur Zeiger zu Speicherbereichen, die gerade freigeworden sind, andere zählen einfach immer hoch, wieder andere sind Indexe in einem Array. Also das kann alles mögliche sein prinzipiell. Aber normalerweise musst du dir darüber auch keine Sorgen machen wo die ID herkommt. Sie aber definitiv eindeutig für jeden laufenden Thread.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
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: Thread ID Problem auf dem MAC

Beitrag von NicTheQuick »

Noch drei Sachen

Code: Alles auswählen

*Parameters.Person = AllocateMemory(SizeOf(Person))
*Parameters\Name$ = "Peter"
*Parameters\Age   = 22
*Parameters\ThreadID = CreateThread(@Thread(), *Parameters) ; Einen Zeiger auf unsere Struktur an den Thread senden
Delay(1000)
1. Nutze statt AllocateMemory() lieber AllocateStructure().
2. Nutze definitiv FreeMemory() oder eben FreeStructure(). In deinem Beispiel machst du nämlich zweimal AllocateMemory(), aber nur einmal FreeMemory(). Das ist dann ein Speicherleck.
3. Ein ClearStructure() nach einem FreeStructure() kann fatal werden, da versucht wird auf Speicher zuzugreifen, der schon freigegeben wurde. Mit AllocateStructure() und FreeStructure() bist du besser aufgehoben.
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Thread ID Problem auf dem MAC

Beitrag von mk-soft »

KillThread sollte nur aufgerufen werden, wenn dieser nicht mehr regiert.
Das hatte ich bis jetzt nur einmal, als ich ein COM-Objekt verwendete das ohne zu warten seine Ressourcen gelöscht hatte.
Wurde irgendwann vom Hersteller repariert.

Bei macOS müssen immer die Threads selber beendet werden, sonst beendet sich das Programm nicht richtig und kann hängen bleiben.
Unter Window werden diese einfach gekillt, was natürlich auch nicht gut ist.

Die PB eigenen Befehle PauseThread und ResumeThread sollte man auch nicht nutzen, da der Thread dann an einem undefinierten Zustand angehalten wird.
Das ist zum Beispiel sehr ungünstig wenn gerade eine Datei geöffnet ist, aber auf jeden Fall wieder nach den schreiben geschlossen sein muss.

P.S.
FreeStructure gibt nur Daten frei. In der Struktur hinterlegten Strings, Arrays, Listen und Maps werden dann auch freigegeben.
Es werden in der Structure aber hinterlegten IDs, Pointers auf Speicher, Handles nicht freigeben.
Somit muss vor FreeStructure zum Beispiel in der Struktur hinterlegten geöffnete Dateien geschlossen werden oder ein Pointer auf Speicher (AllocateMemory) vorher selber freigeben werde.
Zuletzt geändert von mk-soft am 21.07.2020 12:56, insgesamt 5-mal geändert.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Thread ID Problem auf dem MAC

Beitrag von mk-soft »

Ich habe mal mein Mini Thread Control mit deutschen Kommentaren versehen.

Update Beispiele

Code: Alles auswählen

;-TOP

;- Begin Mini Thread Control

;  by mk-soft, Version 1.08, 20.10.2019, Update examples 21.07.2020

CompilerIf Not #PB_Compiler_Thread
  CompilerError "Use Compiler-Option ThreadSafe!"
CompilerEndIf

; Struktur über dem der Thread gesteuert wird.
; Diese Struktur wird immer mit Extends an die eigene Daten Stuktur erweitern.
; Die muss immer mit Extends als letzte Stuktur hingefügt werden, 
; damit diese immer ganz oben steht.

Structure udtThreadControl
  ThreadID.i
  UserID.i
  Signal.i
  Pause.i
  Exit.i
EndStructure

; Erstellt einen neuen Thread. 
; Das Ergebnis ist umgleich null wenn der Thread erfolgreich angelegt wurde 

Procedure StartThread(*Data.udtThreadControl, *Procedure) ; ThreadID
  If Not IsThread(*Data\ThreadID)
    *Data\Exit = #False
    *Data\Pause = #False
    *Data\ThreadID = CreateThread(*Procedure, *Data)
  EndIf
  ProcedureReturn *Data\ThreadID
EndProcedure

; Stop einen existierenden Thread. Sollte der Thread in Pause sein, wird vorher
; mit einen Signal die Pause beendet

Procedure StopThread(*Data.udtThreadControl, Wait = 1000) ; Void
  If IsThread(*Data\ThreadID)
    *Data\Exit = #True
    If *Data\Pause
      *Data\Pause = #False
      SignalSemaphore(*Data\Signal)
    EndIf
    If Wait
      If WaitThread(*Data\ThreadID, Wait) = 0
        KillThread(*Data\ThreadID)
      EndIf
      *Data\ThreadID = 0
      *Data\Pause = #False
      *Data\Exit = #False
      If *Data\Signal
        FreeSemaphore(*Data\Signal)
        *Data\Signal = 0
      EndIf
    EndIf
  EndIf
EndProcedure

; Gibt den Speicher für den Thread frei. Sollte dieser noch laufen, wird dieser erst gestoppt.

Procedure FreeThread(*Data.udtThreadControl, Stop = #True, Wait = 1000) ; True or False
  If IsThread(*Data\ThreadID)
    If Stop
      StopThread(*Data, Wait)
      FreeStructure(*Data)
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  Else
    If *Data\Signal
      FreeSemaphore(*Data\Signal)
    EndIf
    FreeStructure(*Data)
    ProcedureReturn #True
  EndIf
EndProcedure

; Hält den Thread an. Dafür wird ein Semaphore erstellt auf dem der Thread warten kann.

Procedure ThreadPause(*Data.udtThreadControl) ; Void
  If IsThread(*Data\ThreadID)
    If Not *Data\Signal
      *Data\Signal = CreateSemaphore()
    EndIf
    If Not *Data\Pause
      *Data\Pause = #True
    EndIf
  EndIf
EndProcedure

; Setzt den Thread fort, wenn dieser angehalten wurde. Dazu wird über Semaphore ein Signal ausgelöst.

Procedure ThreadResume(*Data.udtThreadControl) ; Void
  If IsThread(*Data\ThreadID)
    If *Data\Pause
      *Data\Pause = #False
      SignalSemaphore(*Data\Signal)
    EndIf
  EndIf
EndProcedure

;- End Mini Thread Control

; ****

; Example 1: Multi Threads
; Example 2: RunProgram
; Example 3: Send string from thread to GUI

#Example = 1

;- Example 1

CompilerIf #Example = 1
  
  Enumeration #PB_Event_FirstCustomValue
    #MyEvent_ThreadFinished
  EndEnumeration
  
  Enumeration Gadget
    #ButtonStart1
    #ButtonStart2
    #ButtonPauseResume1
    #ButtonPauseResume2
    #ButtonStop1
    #ButtonStop2
  EndEnumeration
  
  Structure udtFileData
    Name.s
    Result.s
  EndStructure
  
  ; Hier die Struktur für alle erforderlich zu bearbeiteten Daten definieren
  
  ; Mit Extends die eigene Stuktur mit der Struktur von Thread Control erweitern
  ; Extends setzt immer die zu erweiterten Stuktur an den Anfang der Struktur
  
  Structure udtThreadData Extends udtThreadControl
    ; Data
    Window.i
    Event.i
    List Files.udtFileData()
  EndStructure
  
  ; ----------
  
  Procedure MyThread(*Data.udtThreadData)
    Protected c
    
    With *Data
      Debug "Init Thread " + \UserID
      ; TODO 
      ; Daten vorbereiten
      Delay(500)
      
      Debug "Start Thread " + \UserID
      ; TODO
      ; Hier beginnt die zyklische Schleife (For .. next, Repeat .. forever, etc)
      ForEach \Files()
        ; 1. Abfragen ob der Thread in Pause gehen soll
        If \Pause
          Debug "Pause Thread " + \UserID
          ; TODO
          ; Hier die Bearbeitung bevor der Thread in Pause gehen kann
          
          ; Hier wartet der Thread auf ein Signal zum fortsetzen
          WaitSemaphore(\Signal)
          ; Abfragen ob der Thread nach der Pause sofort beendet werde soll
          If \Exit
            Break ; Hier wird beim beenden die Schleife verlassen
          EndIf    
          Debug "Resume Thread " + \UserID
          ; TODO
          ; Hier die Bearbeitung bevor der Threads forgesetzt werden kann
        EndIf
        ; 2. Abfragen ob der Thread beendet werden soll
        If \Exit
          Break ; Hier wird beim vorzeitigen beenden die Schleife verlassen
        EndIf
        ; TODO
        ; Hier die Daten bearbeiten.
        ; Aber immer nur ein Datensatz und keine Endlos Schleifen. Sonst reagiert der Thread nicht mehr.
        Debug "Busy Thread " + \UserID + ": File " + \Files()\Name
        Delay(500)
        \Files()\Result = "Ready."
      Next
      ; Hier endet die zyklische Schleife
      If \Exit
        ; TODO
        ; Hier die Berbeitung bei vorzeitigen beenden des Threads
        Debug "Cancel Thread " + \UserID
      Else
        ; TODO
        ; Hier die Bearbeitung bei normalen beenden des Threads
        Debug "Shutdown Thread " + \UserID
        PostEvent(\Event, \Window, 0, 0, *Data) ; <- EventData = Pointer to ThreadData
      EndIf
      
      Debug "Exit Thread " + \UserID
      ; Hier die globale Bearbeitung beim beenden des Threads
      
      ; 3. Die eigene ThreadID löschen. Letzte Aufgabe des Threads
      \ThreadID = 0
    EndWith
  EndProcedure
  
  ; ----------
  
  ; Die Daten für den Thread immer mit AllocateStructure anlegen,
  ; damit FreeThread die Daten auch wieder löschen kann.
  
  Global *th1.udtThreadData = AllocateStructure(udtThreadData)
  *th1\UserID = 1
  *th1\Window = 1
  *th1\Event = #MyEvent_ThreadFinished
  For i = 10 To 30
    AddElement(*th1\Files())
    *th1\Files()\Name = "Data_" + i
  Next
  
  Global *th2.udtThreadData = AllocateStructure(udtThreadData)
  *th2\UserID = 2
  *th2\Window = 1
  *th2\Event = #MyEvent_ThreadFinished
  For i = 31 To 60
    AddElement(*th2\Files())
    *th2\Files()\Name = "Data_" + i
  Next
  
  ; Output Data
  Procedure Output(*Data.udtThreadData)
    Debug "Thread Finished UserID " + *Data\UserID
    MessageRequester("Thread Message", "Thread Finished UserID " + *Data\UserID)
    ForEach *Data\Files()
      Debug *Data\Files()\Name + " - Result " + *Data\Files()\Result
    Next
  EndProcedure
  
  If OpenWindow(1, 0, 0, 222, 250, "Mini Thread Control", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    ButtonGadget(#ButtonStart1, 10, 10, 200, 30, "Start 1")
    ButtonGadget(#ButtonStart2, 10, 50, 200, 30, "Start 2")
    ButtonGadget(#ButtonPauseResume1, 10, 90, 200, 30, "Pause 1")
    ButtonGadget(#ButtonPauseResume2, 10, 130, 200, 30, "Pause 2")
    ButtonGadget(#ButtonStop1, 10, 170, 200, 30, "Stop 1")
    ButtonGadget(#ButtonStop2, 10, 210, 200, 30, "Stop 2")
    Repeat
      Select WaitWindowEvent() 
        Case #PB_Event_CloseWindow
          FreeThread(*th1)
          FreeThread(*th2)
          Break
        Case #PB_Event_Gadget
          Select EventGadget()
            Case #ButtonStart1
              StartThread(*th1, @MyThread())
            Case #ButtonStart2
              StartThread(*th2, @MyThread())
            Case #ButtonPauseResume1
              If IsThread(*th1\ThreadID)
                If Not *th1\Pause
                  ThreadPause(*th1)
                  SetGadgetText(#ButtonPauseResume1, "Resume 1")
                Else
                  ThreadResume(*th1)
                  SetGadgetText(#ButtonPauseResume1, "Pause 1")
                EndIf
              EndIf
            Case #ButtonPauseResume2
              If IsThread(*th2\ThreadID)
                If Not *th2\Pause
                  ThreadPause(*th2)
                  SetGadgetText(#ButtonPauseResume2, "Resume 2")
                Else
                  ThreadResume(*th2)
                  SetGadgetText(#ButtonPauseResume2, "Pause 2")
                EndIf
              EndIf
            Case #ButtonStop1
              StopThread(*th1)
              SetGadgetText(#ButtonPauseResume1, "Pause 1")
            Case #ButtonStop2
              StopThread(*th2)
              SetGadgetText(#ButtonPauseResume2, "Pause 2")
          EndSelect
          
        Case #MyEvent_ThreadFinished
          Output(EventData())
          
      EndSelect
    ForEver
  EndIf
  
CompilerEndIf

;- Example 2

CompilerIf #Example = 2 
  
  Enumeration #PB_Event_FirstCustomValue
    #MyEvent_ThreadFinished
  EndEnumeration
  
  Enumeration Gadget
    #ButtonStart1
    #ButtonPauseResume1
    #ButtonStop1
  EndEnumeration
  
  ; Extends always own data structure with structure from thread control
  Structure udtThreadData Extends udtThreadControl
    ; Data
    Window.i
    Event.i
    Output.s
  EndStructure
  
  Procedure MyThread(*Data.udtThreadData)
    Protected Compiler, Output.s
    
    With *Data
      Debug "Init Thread " + \UserID
      ;- Begin Thread init
      url.s = "Item1"+#LF$+"Item2"+#LF$
      ;Compiler = RunProgram(#PB_Compiler_Home+"./php", "-r 'echo rawurlencode(" + url + ").PHP_EOL;'", "", #PB_Program_Open | #PB_Program_Read)
      Compiler = RunProgram(#PB_Compiler_Home+"/Compilers/pbcompiler", "-h", "", #PB_Program_Open | #PB_Program_Read)
      Output = ""
      ;- End Thread init
      Debug "Start Thread " + \UserID
      ;- Begin Thread loop
      If Compiler
        While ProgramRunning(Compiler)
          ; 1. Query on thread pause
          If \Pause
            Debug "Pause Thread " + \UserID
            WaitSemaphore(\Signal)
            ; Query on thread cancel
            If \Exit
              Break ; Exit Thread loop
            EndIf
            Debug "Resume Thread " + \UserID
          EndIf
          ; 2. Query on thread cancel
          If \Exit
            Break ; Exit Thread loop
          EndIf
          ; Input
          If AvailableProgramOutput(Compiler)
            Output + ReadProgramString(Compiler) + Chr(13)
          Else
            Delay(10)
          EndIf
        Wend
        Output + Chr(13) + Chr(13)
        Output + "Exitcode: " + Str(ProgramExitCode(Compiler))
        CloseProgram(Compiler) ; Close the connection to the program
      Else
        Output = "Error - Programm konnte nicht gestartet werden!"
      EndIf
      ;- End Thread lopp
      If \Exit
        ;- TODO Thread Cancel
        Debug "Cancel Thread " + \UserID
        \Output = "Error - Thread vom user abgebrochen!"
      Else
        ;- TODO Thread Finished
        Debug "Shutdown Thread " + \UserID
        \Output = Output
        PostEvent(\Event, \Window, 0, 0, *Data) ; <- EventData = Pointer to ThreadData
      EndIf
      Debug "Exit Thread " + \UserID
      ; 3. Clear ThreadID 
      \ThreadID = 0
    EndWith
  EndProcedure
  
  ; Create Data always with AllocateStructure  
  Global *th1.udtThreadData = AllocateStructure(udtThreadData)
  *th1\UserID = 1
  *th1\Window = 1
  *th1\Event = #MyEvent_ThreadFinished
  
  ; Output Data
  Procedure Output(*Data.udtThreadData)
    Debug "Thread Finished UserID " + *Data\UserID
    MessageRequester("Thread Message", "Thread Finished " + #LF$ + #LF$ + *Data\Output)
  EndProcedure
  
  If OpenWindow(1, 0, 0, 222, 250, "Mini Thread Control", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    ButtonGadget(#ButtonStart1, 10, 10, 200, 30, "Start 1")
    ButtonGadget(#ButtonPauseResume1, 10, 90, 200, 30, "Pause 1")
    ButtonGadget(#ButtonStop1, 10, 170, 200, 30, "Stop 1")
    Repeat
      Select WaitWindowEvent() 
        Case #PB_Event_CloseWindow
          FreeThread(*th1)
          Break
        Case #PB_Event_Gadget
          Select EventGadget()
            Case #ButtonStart1
              StartThread(*th1, @MyThread())
            Case #ButtonPauseResume1
              If IsThread(*th1\ThreadID)
                If Not *th1\Pause
                  ThreadPause(*th1)
                  SetGadgetText(#ButtonPauseResume1, "Resume 1")
                Else
                  ThreadResume(*th1)
                  SetGadgetText(#ButtonPauseResume1, "Pause 1")
                EndIf
              EndIf
            Case #ButtonStop1
              StopThread(*th1)
              SetGadgetText(#ButtonPauseResume1, "Pause 1")
          EndSelect
          
        Case #MyEvent_ThreadFinished
          Output(EventData())
          
      EndSelect
    ForEver
  EndIf
  
CompilerEndIf

;- Example 3

CompilerIf #Example = 3
  
  ; ---- String Helper ----
  
  Procedure AllocateString(String.s) ; Result = Pointer
    Protected *mem.string = AllocateStructure(String)
    If *mem
      *mem\s = String
    EndIf
    ProcedureReturn *mem
  EndProcedure
  
  Procedure.s FreeString(*mem.string) ; Result String
    Protected r1.s
    If *mem
      r1 = *mem\s
      FreeStructure(*mem)
    EndIf
    ProcedureReturn r1
  EndProcedure
  
  ; ----
  
  
  Enumeration #PB_Event_FirstCustomValue
    #MyEvent_ThreadSendString
    #MyEvent_ThreadFinished
  EndEnumeration
  
  Enumeration Gadget
    #List
    #ButtonStart1
    #ButtonPauseResume1
    #ButtonStop1
  EndEnumeration
  
  
  ; Extends always own data structure with structure from thread control
  Structure udtThreadData Extends udtThreadControl
    ; Data
    Window.i
    Event.i
  EndStructure
  
  Procedure MyThread(*Data.udtThreadData)
    Protected cnt, stringdata.s
    
    ; Send string over PostEvent and parameter EventData
        
    With *Data
      stringdata = "Init Thread " + \UserID
      PostEvent(#MyEvent_ThreadSendString, \Window, 0, 0, AllocateString(stringdata))
      ;TODO
      Delay(500)
      stringdata = "Start Thread " + \UserID
      PostEvent(#MyEvent_ThreadSendString, \Window, 0, 0, AllocateString(stringdata))
      ;TODO
      Repeat
        ; 1. Query on thread pause
        If \Pause
          stringdata = "Pause Thread " + \UserID
          PostEvent(#MyEvent_ThreadSendString, \Window, 0, 0, AllocateString(stringdata))
          WaitSemaphore(\Signal)
          ; Query on thread cancel
          If \Exit
            Break ; Exit Thread loop
          EndIf
          stringdata = "Resume Thread " + \UserID
          PostEvent(#MyEvent_ThreadSendString, \Window, 0, 0, AllocateString(stringdata))
        EndIf
        ; 2. Query on thread cancel
        If \Exit
          Break ; Exit Thread loop
        EndIf
        ;TODO Cyle Process
        cnt + 1
        stringdata = "Busy Thread " + \UserID + ": Count " + cnt
        PostEvent(#MyEvent_ThreadSendString, \Window, 0, 0, AllocateString(stringdata))
        Delay(500)
      ForEver
      
      If \Exit
        ;TODO Thread Cancel
        stringdata = "Cancel Thread " + \UserID
        PostEvent(#MyEvent_ThreadSendString, \Window, 0, 0, AllocateString(stringdata))
      Else
        ;TODO Thread Finished
        stringdata = "Finished Thread " + \UserID
        PostEvent(#MyEvent_ThreadSendString, \Window, 0, 0, AllocateString(stringdata))
      EndIf
      
      ; 3. Clear ThreadID 
      \ThreadID = 0
    EndWith
  EndProcedure
  
  ; Create Data always with AllocateStructure  
  Global *th1.udtThreadData = AllocateStructure(udtThreadData)
  *th1\UserID = 1
  *th1\Window = 1
  
  If OpenWindow(1, 50, 50, 600, 400, "Mini Thread Control", #PB_Window_SystemMenu)
    ListViewGadget(#List, 5, 5, 590, 360)
    ButtonGadget(#ButtonStart1, 5, 365, 120, 30, "Start")
    ButtonGadget(#ButtonPauseResume1, 130, 365, 120, 30, "Pause")
    ButtonGadget(#ButtonStop1, 255, 365, 120, 30, "Stop")
    Repeat
      Select WaitWindowEvent() 
        Case #PB_Event_CloseWindow
          FreeThread(*th1)
          Break
        Case #PB_Event_Gadget
          Select EventGadget()
            Case #ButtonStart1
              StartThread(*th1, @MyThread())
            Case #ButtonPauseResume1
              If IsThread(*th1\ThreadID)
                If Not *th1\Pause
                  ThreadPause(*th1)
                  SetGadgetText(#ButtonPauseResume1, "Resume")
                Else
                  ThreadResume(*th1)
                  SetGadgetText(#ButtonPauseResume1, "Pause")
                EndIf
              EndIf
            Case #ButtonStop1
              StopThread(*th1)
              SetGadgetText(#ButtonPauseResume1, "Pause")
              
          EndSelect
          
        Case #MyEvent_ThreadSendString
          ; Receive string over event data
          AddGadgetItem(#List, -1, FreeString(EventData()))
          ; Small trick to move last item
          SetGadgetState(#List, CountGadgetItems(#List) - 1)
          SetGadgetState(#List, -1)
          
      EndSelect
    ForEver
  EndIf
  
CompilerEndIf
Zuletzt geändert von mk-soft am 21.07.2020 13:19, insgesamt 1-mal geändert.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
ChristianH
Beiträge: 8
Registriert: 21.06.2020 20:15

Re: Thread ID Problem auf dem MAC

Beitrag von ChristianH »

Hi NicTheQuick und Michael,

danke für eure hilfreichen Beiträge.

Zu meinem Beispiel folgende Info. Es macht einen großen Unterschied welchen Delay Wert ich benutze und damit den Ablauf beeinflusse. NicTheQuick hat da vollkommen Recht das man teilweise "spannende" Ergebnisse hat und dadurch gleiche Thread Id s bekommt. Im Normalfall sind diese immer unterschiedlich, werden aber offensichtlich nochmal verwendet.

Sprich ein Thread hat die Nummer 12345 und wird beendet. Der neue Thread bekommt dann (unter Umständen) die gleiche Nummer noch einmal. Als Anfänger durchblickt man das nicht sofort und fühlt sich dann "leicht verwirrt" :lol:

Michal Dir vielen Dank für Deine hilfreichen Beispiele. Auch wenn diese mich immer wieder vor neue Herausforderungen stellen. Aber das macht ja auch Spaß. :allright:

Ich werde mal weiter lesen und lernen.

Mit einer Hilfsvariable zu arbeiten welche unique für jeden Thread ist macht wohl den meisten Sinn. Diese kann ja mit dem Befehl "Threaded" definiert werden und wird dann nicht überschrieben. Mit dieser Variable kann man dann innerhalb des Threads prüfen ob der jeweilige Thread sich selbst beenden soll. Oder wenn man alle Threads gleichzeitig beenden will eine globale Variable für alle.


Eine Sache habe ich noch nicht verstanden. In meinem Beispiel ist es durchaus so das diese Thread ID innerhalb der Procedure (des Thread sichtbar ist. Aber NicTheQuick schreibt in seinem Beitrag
Im Mai hab ich im englischen Forum eine Möglichkeit gepostet wie ein Thread sicher an seine eigene ID kommen kann: Get own threadID without passing as parameter
Gilt das vielleicht nicht für den Mac oder was ist der Grund? Oder ist es einfach Glück das es in meinem Beispiel klappt?

Hier noch mal mein (lauffähiger aber nicht optimaler) Code mit dem ich dies sehe wenn ich mir die Debug Ausgabe ansehe. Es wäre super wenn mir dabei jemand helfen kann dieses zu verstehen.

Gruß Christian

Code: Alles auswählen

Threaded Tcontrol.i
Threaded Ti.i

Structure Person
    Name$
    Age.b
    ThreadID.i
  EndStructure
  

  
 
CompilerIf Not #PB_Compiler_Debugger
CompilerError "Please enable the debugger!"
CompilerEndIf


 
  
 
Procedure Thread(*Parameters.Person)
    Ti.i = 1
    Tcontrol.i = 0
    While Tcontrol.i = 0     
    
    
    Debug *Parameters\Name$ + " Procedure " + *Parameters\ThreadID
    Delay(10)
    Ti.i = Ti.i + 1  
        
          If Ti.i > 20
            Tcontrol.i = 1
          EndIf
     Wend 
    Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++Ende Procedure mit der Thread ID " + *Parameters\ThreadID
    KillThread(*Parameters\ThreadID.i) 
    
  EndProcedure
  

  *Parameters.Person = AllocateMemory(SizeOf(Person))
  *Parameters\Name$ = "Peter"
  *Parameters\Age   = 22
  *Parameters\ThreadID.i = CreateThread(@Thread(), *Parameters) 
   Delay(100)  
   Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++ Main Programm 1 Aufruf Thread ID " + *Parameters\ThreadID
  
  *Parameters.Person = AllocateMemory(SizeOf(Person))
  *Parameters\Name$ = "Ulf    "
  *Parameters\Age   = 33
  *Parameters\ThreadID.i = CreateThread(@Thread(), *Parameters) ; Einen Zeiger auf unsere Struktur an den Thread senden
   Delay(800)  
   Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++ Main Programm 2 Aufruf Thread ID " + *Parameters\ThreadID
  FreeMemory(*Parameters)
  ClearStructure(*Parameters, Person)
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Thread ID Problem auf dem MAC

Beitrag von mk-soft »

Um die Threads zu unterscheiden gibt es im meiner Struktur eine Hilfe Variable 'UserID' die man dafür verwenden kann.
Die ThreadID in der Struktur sollte nicht innerhalb des Threads verwendet werden. Diese ist dort nur gut aufgehoben,
damit man diese immer zur Verfügung hat um den Thread abzufragen (IsThread, etc)

Böse: Immer noch KillThread drin

Ich selber finde es immer besser alle erforderlichen Variablen für den Thread in der Struktur zu haben und NIE Globale variablen zu verwenden. Threaded geht natürlich, aber unübersichtlich irgendwann.
Somit kann man mit AllocateStructure mehrere Speicher anlegen und gleichzeitig mehrmals gleichzeitig den Thread starten.

Dein Umgang mit Speicher stimmt noch garnicht ... Du Must für jeden Thread ein eigenen Pointer haben, sonst hast du ein Speicherleck.

Man kann natürlich nicht erst mit FreeMemory den Speicher freigeben und dann mit ClearStructure auf den Speicher zugreifen.
Zuletzt geändert von mk-soft am 21.07.2020 14:07, insgesamt 4-mal geändert.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Thread ID Problem auf dem MAC

Beitrag von mk-soft »

Mal in schnelle etwas überarbeitet

Code: Alles auswählen

CompilerIf Not #PB_Compiler_Thread
  CompilerError "Use Compiler-Option ThreadSafe!"
CompilerEndIf

CompilerIf Not #PB_Compiler_Debugger
  CompilerError "Please enable the debugger!"
CompilerEndIf

Threaded Tcontrol.i
Threaded Ti.i

Structure Person
  Name$
  Age.b
  ThreadID.i
EndStructure

Procedure Thread(*Parameters.Person)
  Ti.i = 1
  Tcontrol.i = 0
  While Tcontrol.i = 0     
    
    
    Debug *Parameters\Name$ + " Procedure " + *Parameters\ThreadID
    Delay(100)
    Ti.i = Ti.i + 1  
    
    If Ti.i > 20
      Tcontrol.i = 1
    EndIf
  Wend 
  Debug "++++ Ende Procedure mit der Thread ID " + *Parameters\ThreadID
  
EndProcedure

Define *Param1.Person, *Param2.Person

*Param1 = AllocateStructure(Person)
*Param1\Name$ = "Peter"
*Param1\Age   = 22
*Param1\ThreadID.i = CreateThread(@Thread(), *Param1) 

Debug "++++ Main Programm 1 Aufruf Thread ID " + *Param1\ThreadID

*Param2.Person = AllocateStructure(Person)
*Param2\Name$ = "Ulf    "
*Param2\Age   = 33
*Param2\ThreadID.i = CreateThread(@Thread(), *Param2) ; Einen Zeiger auf unsere Struktur an den Thread senden

Debug "++++ Main Programm 2 Aufruf Thread ID " + *Param2\ThreadID

Repeat
  Delay(100)
Until Not IsThread(*Param1\ThreadID) And Not IsThread(*Param2\ThreadID)

FreeStructure(*Param1)
FreeStructure(*Param2)
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8809
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: Thread ID Problem auf dem MAC

Beitrag von NicTheQuick »

ChristianH hat geschrieben:Eine Sache habe ich noch nicht verstanden. In meinem Beispiel ist es durchaus so das diese Thread ID innerhalb der Procedure (des Thread sichtbar ist. Aber NicTheQuick schreibt in seinem Beitrag
NicTheQuick hat geschrieben:Im Mai hab ich im englischen Forum eine Möglichkeit gepostet wie ein Thread sicher an seine eigene ID kommen kann: Get own threadID without passing as parameter
Gilt das vielleicht nicht für den Mac oder was ist der Grund? Oder ist es einfach Glück das es in meinem Beispiel klappt?
Ja, da hattest du Glück. Oder andersrum gesagt, die meiste Zeit wird es funktionieren. Aber da kannst du dir bei Multithreading nie sicher sein. Um wirklich sicher die Thread-ID innerhalb des Threads zu haben, solltest du den Code von mir aus dem englischen Forum nutzen.
Schon bei diesem einfachen Beispiel (abgeleitet aus deinem Code) kann es ohne Debugger passiert, dass das erste Print 0 ausgibt und das nächste erst die Thread-ID. Es ist sehr unwahrscheinlich, aber theoretisch möglich. Erlebt habe ich es jedenfalls schon.

Code: Alles auswählen

Structure Person
	ThreadID.i
EndStructure

Procedure Thread(*Parameters.Person)
	PrintN(Str(*Parameters\ThreadID))
	Delay(100)
	PrintN(Str(*Parameters\ThreadID))
EndProcedure

If OpenConsole()

	*Parameters.Person = AllocateStructure(Person)
	*Parameters\ThreadID = CreateThread(@Thread(), *Parameters) ; Einen Zeiger auf unsere Struktur an den Thread senden
	Delay(1000) 
	
	FreeStructure(*Parameters)
	
	CloseConsole()
EndIf
Mit einer Hilfsvariable zu arbeiten welche unique für jeden Thread ist macht wohl den meisten Sinn. Diese kann ja mit dem Befehl "Threaded" definiert werden und wird dann nicht überschrieben. Mit dieser Variable kann man dann innerhalb des Threads prüfen ob der jeweilige Thread sich selbst beenden soll. Oder wenn man alle Threads gleichzeitig beenden will eine globale Variable für alle.
'Threaded' ist vor allen dann sinnvoll, wenn du von der initialen Thread-Procedure aus noch andere Procedures aufrufst und du in diesen einen Wert ändern möchtest, der überall sichtbar sein soll.
Antworten