Thread-Pool - Das angegebene 'Thread' ist null

Anfängerfragen zum Programmieren mit PureBasic.
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

Thread-Pool - Das angegebene 'Thread' ist null

Beitrag von uweb »

Um im Bedarfsfall nicht erst einen Thread erzeugen zu müssen habe ich mir einen Thread-Pool gebastelt.
So neu ist die Idee nicht. Ich wollte ihn aber von Grund auf selbst bauen.
Zunächst schien er zu laufen. Dann wollte ich aber sehen wieviel schneller er nun ist.
Nun bin ich an dem Punkt, dass ein neuer Thread sich manchmal nicht selbst auf Pause setzen kann, weil seine Adresse scheinbar noch null ist.
Ich weiß, das klingt blöde. Aber wenn man das Delay in Zeile 44 entfernt kommt spätestens beim 10. Programmaufruf in der IDE eine entsprechende Meldung.
[11:53:17] [ERROR] 002 Thread Benchmark.pb (Zeile: 45)
[11:53:17] [ERROR] Das angegebene 'Thread' ist null.
Schlimmer noch : Als EXE läuft es gar nicht.
Hat irgend jemand eine Idee?

Code: Alles auswählen

CompilerIf #PB_Compiler_Thread
  
  EnableExplicit
  
  Define AnzahlThreads = 100
  Define i, StartTimeA, StartTimeB, ElapsedTimeA, ElapsedTimeB
  Global x
  #New = 0
  #Waiting = 1
  #Working = 2
  
  Structure ThreadStructure
    Thread.i
    Status.i
  EndStructure
  
  Global Dim ThreadInfo.ThreadStructure(AnzahlThreads) ; Basis ist 0 - Thread 0 ist für den Server reserviert
  
  Macro Work(ThreadNumber)
    Debug ThreadNumber
    x+1
  EndMacro 
  
  Macro StartThread(ThreadNumber)
    ;Debug " ThreadInfo("+Str(ThreadNumber)+")\Status = "+ ThreadInfo(ThreadNumber)\Status
    If ThreadInfo(ThreadNumber)\Status = #Working
      Debug "Thread " + Str(ThreadNumber) + " is already working"
      ; hier könnte ein Client eine angestoßene Verarbeitung stoppen bevor sie zu ende ist
    Else
      ResumeThread(ThreadInfo(ThreadNumber)\Thread) ;work
    EndIf 
  EndMacro 
  
  Procedure ThreadA (ThreadNumber)
    Repeat
      If ThreadInfo(ThreadNumber)\Status = #Waiting
        ThreadInfo(ThreadNumber)\Status = #Working
        Work(ThreadNumber)
        ThreadInfo(ThreadNumber)\Status = #Waiting
        PauseThread(ThreadInfo(ThreadNumber)\Thread)
      Else ; ThreadInfo(ThreadNumber)\Status = #New - " wurde gerade als Vorbereitung erzeugt - soll also im Moment noch nichts tun
        Debug "Thread " + Str(ThreadNumber) + " wurde erzeugt"
        ThreadInfo(ThreadNumber)\Status = #Waiting
        Delay (10) ;- sonst kommt es hier vereinzelt zu "[ERROR] Das angegebene 'Thread' ist null."
        PauseThread(ThreadInfo(ThreadNumber)\Thread)
      EndIf 
    ForEver
  EndProcedure
  
  Procedure ThreadB (ThreadNumber)
    Work(ThreadNumber)
  EndProcedure
  
  For i=1 To AnzahlThreads        ; als Vorbereitung ThreadStructure füllen und Thread-Pool erzeugen
    ThreadInfo(i)\Status = #New
    ThreadInfo(i)\Thread = CreateThread(@ThreadA(), i)
  Next i
  Delay (100)
  Debug "-------------"
  Delay (900)
  
  x=0
  StartTimeA = ElapsedMilliseconds()
  For i=1 To AnzahlThreads ; vorbereitete Threads arbeiten lassen
    StartThread(i)
  Next i 
  Repeat : Until x = AnzahlThreads
  Delay (1000) 
  ElapsedTimeA = ElapsedMilliseconds()-StartTimeA-1000

  x=0
  StartTimeB = ElapsedMilliseconds()
  For i=1 To AnzahlThreads ; Threads erzeugen und arbeiten lassen
    CreateThread(@ThreadB(), i)
  Next i 
  Repeat : Until x = AnzahlThreads
  Delay (1000) 
  ElapsedTimeB = ElapsedMilliseconds()-StartTimeB-1000  
 
  MessageRequester("Information", Str(ElapsedTimeA)+" zu "+Str(ElapsedTimeB), #PB_MessageRequester_Ok)
  Delay (100)

  
CompilerElse
  Debug "Bitte 'Thread-sicheren Exekutable erstellen' in den Compiler-Optionen auswählen." 
CompilerEndIf
edit :

Code: Alles auswählen

Repeat : Until ThreadInfo(ThreadNumber)\Thread
geht statt

Code: Alles auswählen

Delay (10)
natürlich auch (ist wahrscheinlich besser) - die EXE läuft aber trotzdem nicht - bleibt hängen, verbraucht aber CPU-Zeit
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-Pool - Das angegebene 'Thread' ist null

Beitrag von NicTheQuick »

Ich könnte dir jetzt eine Funktion heraus suchen, die du innerhalb des Threads aufrufen kannst und die dir das Betriebssystemspezifische Handle des Threads zurück gibt, aber das hilft dir wahrscheinlich wenig. Ansonsten ist deine Delay-Methode eigentlich logisch. Noch sicherer wäre es sie in eine While-Schleife zu packen. Es kann ja durchaus passieren, dass der erstellte Thread schon wesentlich weiter gelaufen ist als das Hauptprogramm, das gerade noch die Thread-ID dem Array zuweisen muss.

Ich sehe gerade, dass du noch einmal editiert hast. Also die Repeat-Until-Schleife funktioniert auch nicht? Das ist komisch. Hast du es schon mal mit weniger Threads ausprobiert? Unter Linux spackt das mit den Threads unter Purebasic nämlich auch etwas rum, wenn man zu viele nutzen möchte.
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

Re: Thread-Pool - Das angegebene 'Thread' ist null

Beitrag von uweb »

Danke für die schnelle Antwort!
Ja, die Wahrscheinlichkeit für den Fehler wächst mit der Anzahl der Threads.
Die Schleifenlösung ist wohl besser als das Delay. Beides hilft ja in der IDE.
Da läuft es ja dann auch mit 1000 Threads.
Vielleicht fängt die IDE ja auch einen ganz anderen Fehler ab - denn ich nicht sehe.
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Thread-Pool - Das angegebene 'Thread' ist null

Beitrag von mk-soft »

Habe mir auch mal ein paar Gedanken gemacht.

PauseThread hält den Thread irgendwo an. Somit erhält man einen undefinierten Zustand.

Habe es mit einen Semaphore gelöst bekommen.

Code: Alles auswählen

; Comment: Example Threadcontrol
; Author:  mk-soft
; Version: v1.0
; Date:    16.06.2015

Enumeration
  #thCmdNothing
  #thCmdStart
  #thCmdStop
  #thCmdExit
EndEnumeration

Enumeration
  #thStartup
  #thRunning
  #thStopping
  #thStopped
  #thShutdown
  #thFinished
EndEnumeration

Structure udtWork
  ; Header
  handle.i
  signal.i
  cmd.i
  state.i
  ; Userdata
  counter.i
  ; ...
EndStructure  

Procedure thWork(*daten.udtWork)
  
  Protected signal
  
  With *daten
    ; Init Thread
    \state = #thStopped
    
    Repeat
      If \state = #thStopped
        WaitSemaphore(\signal)
        signal = 1
      Else
        signal = TrySemaphore(\signal)
      EndIf
      If signal
        Select \cmd
          Case #thCmdStop
            If \state = #thRunning
              \state = #thStopping
              Debug "Stopping..."
              Delay(1000)
              \state = #thStopped
              Debug "Stopped"
            EndIf
            
          Case #thCmdStart
            If \state = #thStopped
              \state = #thStartup
              Debug "Startup..."
              Delay(100)
              \state = #thRunning
              Debug "Running"
            EndIf
            
          Case #thCmdExit
            \state = #thShutdown
            Debug "Shutdown..."
            Delay(100)
            \state = #thFinished
            Debug "Finished"
            Break
        EndSelect
      Else
        ; Cyle Running
        Debug "Cyle Running..." + Str(\counter)
        \counter + 1
        Delay(1000)
        
      EndIf
      
    ForEver
    
    Debug "Exit Thread"
    
  EndWith
  
EndProcedure

Procedure Main()
  
  Protected thread1.udtWork

  If OpenWindow(0, #PB_Any, #PB_Any, 400, 250, "Thread-test", #PB_Window_SystemMenu)
    ButtonGadget(0, 10, 10, 80, 25, "Start")
    ButtonGadget(1, 100, 10, 80, 25, "Stopp")
    ButtonGadget(2, 190, 10, 80, 25, "Exit")
    
    
    thread1\signal = CreateSemaphore(0)
    
    thread1\handle = CreateThread(@thWork(), @thread1)
    
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          Break
          
        Case #PB_Event_Gadget
          Select EventGadget()
            Case 0
              If thread1\state = #thStopped
                thread1\cmd = #thCmdStart
                SignalSemaphore(thread1\signal)
                Debug "Button Start"
              EndIf
              
            Case 1
              If thread1\state = #thRunning
                thread1\cmd = #thCmdStop
                SignalSemaphore(thread1\signal)
                Debug "Button Stop"
              EndIf
              
            Case 2
              If thread1\state = #thStopped
                thread1\cmd = #thCmdExit
                SignalSemaphore(thread1\signal)
                Debug "Button Exit"
              EndIf
            
          EndSelect
          
      EndSelect
    ForEver
  EndIf
  
EndProcedure : Main()

:wink:
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-Pool - Das angegebene 'Thread' ist null

Beitrag von mk-soft »

Mit Debugger habe ich hiermit auch manchmal Probleme. Mit Threadsafe können auch keine Variablen mehr beobachtet werden.

Anlegen von 1000 Thread etwa 90ms
Start von 1000 Thread etwa 10ms

Code: Alles auswählen

;-TOP

; Comment: Example Threadcontrol
; Author:  mk-soft
; Version: v1.2
; Date:    16.06.2015

; ***************************************************************************************

Enumeration
  #thCmdNothing
  #thCmdStart
  #thCmdStop
  #thCmdExit
EndEnumeration

Enumeration
  #thStartup
  #thRunning
  #thStopping
  #thStopped
  #thShutdown
  #thFinished
EndEnumeration

Structure udtWork
  ; Header
  handle.i
  signal.i
  cmd.i
  state.i
  ; Userdata
  counter.i
  ; ...
  start_time.i
  stop_time.i
  exit_time.i
EndStructure  

; ***************************************************************************************

Procedure thWork(*daten.udtWork)
  
  Protected signal
  
  With *daten
    ; Init Thread
    If \signal <> 0
      FreeSemaphore(\signal)
    EndIf
    \signal = CreateSemaphore()
    \state = #thStopped
    ; ...
    
    Repeat
      If \state = #thStopped
        WaitSemaphore(\signal)
        signal = 1
      Else
        signal = TrySemaphore(\signal)
      EndIf
      If signal
        Select \cmd
          Case #thCmdStart
            If \state = #thStopped
              \state = #thStartup
              \start_time = ElapsedMilliseconds()
              Debug "Startup..."
              Delay(100)
              \state = #thRunning
              Debug "Running"
            EndIf
            
          Case #thCmdStop
            If \state = #thRunning
              \state = #thStopping
              \stop_time = ElapsedMilliseconds()
              Debug "Stopping..."
              Delay(1000)
              \state = #thStopped
              Debug "Stopped"
            EndIf
            
          Case #thCmdExit
            \state = #thShutdown
            \exit_time = ElapsedMilliseconds()
            Debug "Shutdown..."
            Delay(100)
            \state = #thFinished
            Debug "Finished"
            Break
        EndSelect
      Else
        ; Cyle Running
        ;Debug "Cyle Running..." + Str(\counter)
        \counter + 1
        Delay(1000)
        
      EndIf
      
    ForEver
    
    ; Release Thread
    FreeSemaphore(\signal)
    ; ...
  EndWith
  
EndProcedure

; ***************************************************************************************

Macro StartThread(thread)
  If thread#\state = #thStopped
    thread#\cmd = #thCmdStart
    SignalSemaphore(thread#\signal)
  EndIf
EndMacro

Macro StopThread(thread)
  If thread#\state = #thRunning
    thread#\cmd = #thCmdStop
    SignalSemaphore(thread#\signal)
  EndIf
EndMacro

Macro ExitThread(thread)
  If thread#\state = #thStopped
    thread#\cmd = #thCmdExit
    SignalSemaphore(thread#\signal)
  EndIf
EndMacro

; ***************************************************************************************

;-Test

Global max = 999
Global Dim threads.udtWork(max)

Procedure Main()
  
  Protected thread1.udtWork
  Protected start, ende, index

  If OpenWindow(0, #PB_Any, #PB_Any, 400, 250, "Thread-test", #PB_Window_SystemMenu)
    ButtonGadget(0, 10, 10, 80, 25, "Start")
    ButtonGadget(1, 100, 10, 80, 25, "Stopp")
    ButtonGadget(2, 190, 10, 80, 25, "Exit")
    
    start = ElapsedMilliseconds()
    For index = 0 To max
      threads(index)\handle = CreateThread(@thWork(), @threads(index))
      ;Delay(10)
    Next
    ende = ElapsedMilliseconds()
    Debug ende - start
    
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          Break
          
        Case #PB_Event_Gadget
          Select EventGadget()
            Case 0
              For index = 0 To max
                StartThread(threads(index))
              Next
              
            Case 1
              For index = 0 To max
                StopThread(threads(index))
              Next
              
            Case 2
              For index = 0 To max
                ExitThread(threads(index))
              Next
              
          EndSelect
          
      EndSelect
    ForEver
  EndIf
  
EndProcedure : Main()

Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

Re: Thread-Pool - Das angegebene 'Thread' ist null

Beitrag von uweb »

Hallo!
Und wenn Du denkst es geht nichts mehr, kommt von irgendwo ein Lichtlein her.
Vielen Dank!

Ich bin gestern noch lange und erfolglos daran gesessen.
Zuletzt habe ich darüber nachgedacht die Aufgaben weiter zu zerlegen und via QueueUserWorkItem zu erledigen,
statt Threads vorzubereiten und in den Bereitschaftsdienst zu nehmen.

PauseThread hätte den Thread in der von mir angedachten Lösung übrigens normalerweise nicht irgendwo angehalten.
Die Idee war, dass sich ein Thread nach Abarbeiten eines Repeat-ForEver-Schleifendurchgangs selbst schlafen legt nachdem er durch \Status = #Waiting seine Arbeitsbereitschaft nach Außen dokumentiert.
Die einzige Ausnahme davon wäre ein Abbruch von Außen gewesen. Dabei wäre der undefinierte Zustand dann kein Problem.

So weit ich das auf die Schnelle beurteilen kann ist Deine Lösung nicht nur viel universeller und schneller sondern auch stabiler.
:allright:

Leider habe ich heute nur wenig Zeit. Ich freue mich aber schon darauf damit zu Lernen, zu Spielen und zu Basteln.
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Thread-Pool - Das angegebene 'Thread' ist null

Beitrag von mk-soft »

Habe noch etwas umgebaut. SignalSemaphore wird jetzt nur zum wecken des Threads benötigt.

Update v1.5

Code: Alles auswählen

;-TOP

; Comment: Example Threadcontrol
; Author:  mk-soft
; Version: v1.5
; Date:    17.06.2015
; Update:  21.05.2016

; ***************************************************************************************

Enumeration
  #thCmdNothing
  #thCmdStart
  #thCmdStop
  #thCmdExit
EndEnumeration

Enumeration
  #thStartup
  #thRunning
  #thStopping
  #thStopped
  #thShutdown
  #thFinished
EndEnumeration

Structure udtWork
  ; Header
  handle.i
  signal.i
  cmd.i
  state.i
  ; Userdata
  counter.i
  ; ...
  start_time.i
  stop_time.i
  exit_time.i
EndStructure  

; ***************************************************************************************

Procedure thWork(*daten.udtWork)
  
  With *daten
    ; Init Thread
    If \signal <> 0
      FreeSemaphore(\signal)
    EndIf
    \signal = CreateSemaphore()
    \state = #thStopped
    ; ...
    
    Repeat
      ; Go Sleeping
      If \state = #thStopped
        WaitSemaphore(\signal)
      EndIf
      ; Check Command
      Select \cmd
        Case #thCmdStart
          \cmd = #thCmdNothing
          If \state = #thStopped
            \state = #thStartup
            ; Code Startup
            \start_time = ElapsedMilliseconds()
            Debug "Startup..."
            Delay(100)
            Debug "Running"
            ; ...
            \state = #thRunning
          EndIf
          
        Case #thCmdStop
          \cmd = #thCmdNothing
          If \state = #thRunning
            \state = #thStopping
            ; Code Stopping
            \stop_time = ElapsedMilliseconds()
            Debug "Stopping..."
            Delay(2000)
            Debug "Stopped"
            ; ...
            \state = #thStopped
          EndIf
          
        Case #thCmdExit
          \cmd = #thCmdNothing
          If \state < #thShutdown
            \state = #thShutdown
            ; Code Shutdown
            \exit_time = ElapsedMilliseconds()
            Debug "Shutdown..."
            Delay(100)
            Debug "Finished"
            ; ...
            \state = #thFinished
            Break
          EndIf
          
        Default
          ; Code Cyle Running
          \counter + 1
          Delay(100)
          ; ...
      EndSelect
      
    ForEver
    
    ; Release Thread
    FreeSemaphore(\signal)
    ; ...
  EndWith
  
EndProcedure

; ***************************************************************************************

Macro StartThread(thread)
  If thread#\state < #thShutdown
    thread#\cmd = #thCmdStart
    SignalSemaphore(thread#\signal)
  EndIf
EndMacro

Macro StopThread(thread)
  If thread#\state < #thShutdown
    thread#\cmd = #thCmdStop
  EndIf
EndMacro

Macro ExitThread(thread)
  If thread#\state < #thShutdown
    thread#\cmd = #thCmdExit
    SignalSemaphore(thread#\signal)
  EndIf
EndMacro

Macro CheckThread(thread)
  thread#\state
EndMacro

; ***************************************************************************************

;-Test

Global max = 4
Global Dim threads.udtWork(max)

Procedure Main()
  
  Protected thread1.udtWork
  Protected start, ende, index

  If OpenWindow(0, #PB_Any, #PB_Any, 400, 250, "Thread-test", #PB_Window_SystemMenu)
    ButtonGadget(0, 10, 10, 80, 25, "Start")
    ButtonGadget(1, 100, 10, 80, 25, "Stopp")
    ButtonGadget(2, 190, 10, 80, 25, "Exit")
    
    start = ElapsedMilliseconds()
    For index = 0 To max
      threads(index)\handle = CreateThread(@thWork(), @threads(index))
      ;Delay(10)
    Next
    ende = ElapsedMilliseconds()
    Debug ende - start
    
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          Break
          
        Case #PB_Event_Gadget
          Select EventGadget()
            Case 0
              For index = 0 To max
                StartThread(threads(index))
              Next
              
            Case 1
              For index = 0 To max
                StopThread(threads(index))
              Next
              
            Case 2
              For index = 0 To max
                ExitThread(threads(index))
              Next
              
          EndSelect
          
      EndSelect
    ForEver
  EndIf
  
EndProcedure : Main()
P.S. Kommentare angepasst
Zuletzt geändert von mk-soft am 04.07.2017 19:53, insgesamt 3-mal geändert.
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-Pool - Das angegebene 'Thread' ist null

Beitrag von NicTheQuick »

Wenn ich es auf 100 Threads stellt, und dann schnell auf Start, Stopp und wieder Start klicke, dann bleiben sie gestoppt. In der Praxis kann es ja durchaus öfter vorkommen, dass das passiert. Von daher könntest du das noch verbessern.
Benutzeravatar
mk-soft
Beiträge: 3855
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Thread-Pool - Das angegebene 'Thread' ist null

Beitrag von mk-soft »

Das passiert auch bei 10 Threads.
Stopping dauert bei dem Beispiel ja auch 1 Sekunde. Es kann aber nur gestartet werden der Thread im "Stopped" ist.

P.S. Siehe Macro StartThread(...)

Kann man ja so ändern das "Stopped" ausgewertet wird.

P.S 2
Ok... habe ich jetzt angepasst.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

Re: Thread-Pool - Das angegebene 'Thread' ist null

Beitrag von uweb »

Die Lösung von mk-soft finde ich wie schon gesagt super.
Allerdings ging es mir weniger um die Kontrolle über die Threads.
Ich wollte einfach nur mehr Geschwindigkeit erreichen indem ich Threads erzeuge bevor es zeitkritisch wird.
Eigentlich ist es in meinem Fall gar nicht nötig sie von außen zu stoppen.
Wenn ein Client abbricht ist es nicht mehr zeitkritisch und ich kann sie einfach laufen lassen bis sie sich selbst schlafen legen.
Dann stehen sie mir anschließend bei Bedarf wieder zur Verfügung ohne dass ich sie neu erzeugen muss.

In der Windows-API habe ich ein paar interesante Sachen gefunden :
(Fürs Anfänger-Forum mit Erklärung)

Mittels TerminateThread_() könnte ich sie aber auch killen.

Mit GetCurrentThread_() kann der Thread sich jederzeit selbst Schlafen legen - ist also nicht auf den Rückgabewert von CreateThread_() angewiesen.

Da ich mich aber nun schon einmal mit der Windows-API beschäftigt habe bin ich sogar darauf gestoßen, dass ich mit #CREATE_SUSPENDED Threads erzeugen kann, die nicht sofort los laufen.
Dadurch spare ich mir zusätzlich die IF-Abfrage im Thread.

SuspendThread_() spendiert dem Thread eine Schlaftablette und gibt den alten Suspend-Wert (vor dieser Schlaftablette) zurück.
Bei drei Schlaftabletten muß er auch drei mal geweckt werden bevor er anfängt zu arbeiten.
Dabei ändert sich der Suspend-Wert immer auf den vorherigen Wert.

Der Wecker nennt sich ResumeThread_(hThread).
Er gibt auch den alten Suspend-Wert (in dem Fall vor dem Weckruf) zurück.
Auf dem Weg zu der vorläufigen Lösung habe ich zeitweise folgendes verwendet :

Code: Alles auswählen

Macro  WakeUpThread (hThread)
  Select ResumeThread_(hThread)
    Case -1
      Debug "Thread lässt sich nicht wecken."
    Case 1 To #MAXLONG
      Debug "Thread wurde geweckt."
    Case 0
      Debug "Thread war schon wach."    
    Default
      Debug "Unbekannter Rückgabewert von ResumeThread in Macro WakeUpThread"   
  EndSelect
EndMacro
Da ich bei den Signalen noch nicht ganz durch gestiegen bin, nutze ich einfach PostEvent().

Die aktuelle Lösung scheint halbwegs stabil zu sein
- wenn man es nicht übertreibt
- wenn man es mit massenweisem Aufruf von CreateThread_() vergleicht.

Allerdings ist der Geschwindigkeitsvorteil nicht immer gegeben.
Es hängt davon ab wieviel Arbeit auf wieviele Threads verteilt wird und wie lange ein einzelner Thread dafür braucht.

Code: Alles auswählen

#SleepTime = 1
#ThreadQuantity = 256
#JobQuantity = 1024

; #SleepTime = 1
; #ThreadQuantity = 16
; #JobQuantity = 1024

; #SleepTime = 10
; #ThreadQuantity = 16
; #JobQuantity = 1024
Nochmal : Die Lösung von mk-soft hat einige Vorteile. Vor Allem ist sie universeller.
Die vorläufige Lösung für meinen speziellen Fall :

Code: Alles auswählen

;-TOP
;CompilerIf #PB_Compiler_Thread

DisableDebugger

EnableExplicit

Global idThread.l, dwExitCode.l, x, TextGadgetText.s
Define i, Event, Quit

#SleepTime = 1
#ThreadQuantity = 256
#JobQuantity = 1024

; #SleepTime = 1
; #ThreadQuantity = 16
; #JobQuantity = 1024

; #SleepTime = 10
; #ThreadQuantity = 16
; #JobQuantity = 1024

Enumeration #PB_Event_FirstCustomValue
  #ThreadReady
  #NewText
EndEnumeration

Global Dim ThreadHandles(#ThreadQuantity-1)


Procedure ThreadA()
  Repeat 
    Sleep_(#SleepTime) ; Arbeitszeit simulieren um die Threads gleichmäßig auszulasten - läuft auch ohne stabil
    Debug x
    PostEvent(#ThreadReady)
    SuspendThread_(GetCurrentThread_()) 
  ForEver 
EndProcedure 

Procedure Thread() 
  Sleep_(#SleepTime) ; Arbeitszeit simulieren 
  Debug x
  PostEvent(#ThreadReady)
EndProcedure 


Procedure Test() 
  Protected i, j
  Protected StartTimeA, StartTimeB, StartTimeC, ElapsedTimeA, ElapsedTimeB, ElapsedTimeC
  
  ; Das sequentielle CreateThread_() wird als erstes getestet um sicher zu sein, dass keine Altlasten bremsen.
  x = 0
  TextGadgetText = "Teste sequentielles CreateThread_()"
  PostEvent(#NewText)
  Delay(100)
  
  StartTimeB = ElapsedMilliseconds()
  For i = 1 To #JobQuantity
    Debug "B Job : "+Str(i)
    CreateThread_(0, 0, @Thread(), 0, 0, @idThread)
  Next i
  Repeat: Until x = #JobQuantity
  ElapsedTimeB = ElapsedMilliseconds()-StartTimeB
  
  
  x = 0
  TextGadgetText = "Teste vorbereitete Threads"
  PostEvent(#NewText)
  Delay(100)
  
  StartTimeA = ElapsedMilliseconds()
  For i = 1 To #JobQuantity
    Debug "A Job : "+Str(i)
    Repeat     
      For j = 0 To #ThreadQuantity-1
        If ResumeThread_(ThreadHandles(j)) >0 : Break 2 : EndIf
      Next j
    ForEver
  Next i
  Repeat: Until x = #JobQuantity
  ElapsedTimeA = ElapsedMilliseconds()-StartTimeA
  
;   For j = 0 To #ThreadQuantity-1
;     Repeat: Until TerminateThread_(ThreadHandles(j),@dwExitCode)
;   Next j
  
  
  TextGadgetText = "Fertig!"
  PostEvent(#NewText)
  
  If MessageRequester("Vorbereitete Threads gegenüber einfachem CreateThread_()", Str(ElapsedTimeA)+" zu "+Str(ElapsedTimeB) + Chr(10) + "ohne Threads wäres es wohl etwas über "+Str(#SleepTime * #JobQuantity), #PB_MessageRequester_Ok)
    PostEvent(#PB_Event_CloseWindow)
  EndIf
  
EndProcedure 


If OpenWindow(0, 100, 200, 200, 50, "Please wait")
  TextGadget(1, 0,  20, 200, 30, "Vorbereitung ...", #PB_Text_Center)
  
  For i = 0 To #ThreadQuantity-1 ; Threads vorbereiten
    ThreadHandles(i) = CreateThread_(0, 0, @ThreadA(), 0, #CREATE_SUSPENDED, @idThread) 
    ; #CREATE_SUSPENDED -> "Threads werden erzeugt - arbeiten aber noch nicht"
  Next i 
  
  For i = 0 To #ThreadQuantity-1 ; überprüfen ob alle Threads startbereit sind
    If ThreadHandles(i)
      Repeat : Until SuspendThread_(ThreadHandles(i)) >0
      ResumeThread_(ThreadHandles(i)) ; Suspend-Wert zurück setzen
    EndIf
  Next i
  
  CreateThread_(0, 0, @Test(), 0, 0, @idThread)
  
  Repeat
    Event = WaitWindowEvent()
    Select Event
      Case #PB_Event_CloseWindow
        Quit = 1
      Case #ThreadReady
        x+1
      Case #NewText
        SetGadgetText(1, TextGadgetText)
    EndSelect
  Until Quit = 1
EndIf

; CompilerElse
;   Debug "Bitte 'Thread-sicheren Exekutable erstellen' in den Compiler-Optionen auswählen." 
; CompilerEndIf
Antworten