How to pass a string to a thread

Just starting out? Need help? Post your questions and find answers here.
fluent
User
User
Posts: 69
Joined: Sun Jan 24, 2021 10:57 am

How to pass a string to a thread

Post by fluent »

Confused here... Would love any help :)
Am OK with a simple (quick and dirty) fix, to keep the code short and easy to understand

I think I am having 2 issues: the string gets corrupted when being passed to the thread, and all 3 threads seem to be getting the same value...

Thanks!

Code: Select all

; Threaded IPAddress$
; Threaded IP__$

Procedure DoIt(*x)

  IPAddress$ = peekS(*x)
    
  debug "> " + IPAddress$
  
EndProcedure   


procedure.s LookUp(IP$)
  *ip = @IP$
  CreateThread(@DoIt(), *ip)
  
  ProcedureReturn
EndProcedure

LookUp("8.8.8.8")
LookUp("204.79.197.200")
LookUp("162.125.65.13")

delay(3000)
User avatar
mk-soft
Addict
Addict
Posts: 3201
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: How to pass a string to a thread

Post by mk-soft »

For data exchange with threads use structures and allocate structure.

Code: Select all

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

; Threaded IPAddress$
; Threaded IP__$

Structure udtThreadData
  IP.s
EndStructure


Procedure DoIt(*data.udtThreadData)
  
  With *data
    Debug "> " + \IP
  EndWith
  FreeStructure(*data) ; Free structured memory for avoid memory leak
  
EndProcedure   


Procedure.s LookUp(IP$)
  *data.udtThreadData = AllocateStructure(udtThreadData)
  *data\IP = IP$
  CreateThread(@DoIt(), *data)
  
  ProcedureReturn
EndProcedure

LookUp("8.8.8.8")
LookUp("204.79.197.200")
LookUp("162.125.65.13")

Delay(3000)
P.S.
Tipp for works with threads. Link Mini Thread Control
My Projects ThreadToGUI / OOP-BaseClass / OOP-BaseClassDispatch / EventDesigner V3
PB v3.30 / v5.70 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace
fluent
User
User
Posts: 69
Joined: Sun Jan 24, 2021 10:57 am

Re: How to pass a string to a thread

Post by fluent »

Thanks mk-soft. When I run your solution, I am sometimes getting a crash on the very last line (the Delay() statement)
The crash seems to be random, but if I run the code 10-20 times I always see it eventually
User avatar
mk-soft
Addict
Addict
Posts: 3201
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: How to pass a string to a thread

Post by mk-soft »

Not for me, but the programme must always run until the last thread is finished. Even if Purebasic automatically kills the threads that are still running, this can lead to a crash.
Also, it is always best to work with the ThreadSafe option.

Another small change. Always declare the variables in procedures.
And important! Use EnableExplicit.

Code: Select all

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

EnableExplicit

; Threaded IPAddress$
; Threaded IP__$

Structure udtThreadData
  IP.s
EndStructure


Procedure DoIt(*data.udtThreadData)
  With *data
    Debug "> " + \IP
  EndWith
  FreeStructure(*data) ; Free structured memory for avoid memory leak
  
EndProcedure   


Procedure.s LookUp(IP$)
  Protected *Data.udtThreadData = AllocateStructure(udtThreadData)
  *data\IP = IP$
  CreateThread(@DoIt(), *data)
EndProcedure

Define i
For i = 1 To 10
  LookUp("8.8.8.8")
  LookUp("204.79.197.200")
  LookUp("162.125.65.13")
  Delay(100)
Next

Delay(1000)
My Projects ThreadToGUI / OOP-BaseClass / OOP-BaseClassDispatch / EventDesigner V3
PB v3.30 / v5.70 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace
BarryG
Addict
Addict
Posts: 1719
Joined: Thu Apr 18, 2019 8:17 am

Re: How to pass a string to a thread

Post by BarryG »

fluent wrote:the string gets corrupted when being passed to the thread, and all 3 threads seem to be getting the same value
I've had this issue in the past. It happens because the string value gets lost after the thread starts, ie. it isn't a valid string anymore.

You just need a small delay after creating the thread, like below.

Code: Select all

Procedure DoIt(*x)
  IPAddress$ = PeekS(*x)
  Debug "> " + IPAddress$
EndProcedure   

Procedure.s LookUp(IP$)
  *ip = @IP$
  CreateThread(@DoIt(), *ip)
  Delay(100) ; <- Quick and dirty fix. :)
  ProcedureReturn
EndProcedure

LookUp("8.8.8.8")
LookUp("204.79.197.200")
LookUp("162.125.65.13")

Delay(3000)
fluent
User
User
Posts: 69
Joined: Sun Jan 24, 2021 10:57 am

Re: How to pass a string to a thread

Post by fluent »

Thanks, good tips!
User avatar
mk-soft
Addict
Addict
Posts: 3201
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: How to pass a string to a thread

Post by mk-soft »

I would not rely on a pause for the pointer also the string.
Better to pass a copy of the string as a pointer.

Update with ThreadSafe Counter as Object

Code: Select all

;-TOP

; Comment : Object ThreadSafe Counter
; Author  : mk-soft
; Version : v0.01.0
; Create  : 13.02.2021

DeclareModule Counter
  Interface iCounter
    Free(Force = #False)
    Incr()
    Decr()
    Get()
  EndInterface
  
  Declare New()
  
  ; For direct call without interface
  Declare Free(*this, Force = #False)
  Declare Incr(*this)
  Declare Decr(*this)
  Declare Get(*this)
  
EndDeclareModule

Module Counter
  
  Structure udtCounter
    *vTable
    counter.i
    mutex.i
  EndStructure
  
  Procedure New()
    Protected *mem.udtCounter
    *mem = AllocateStructure(udtCounter)
    If *mem
      *mem\vTable = ?vTable
      *mem\mutex = CreateMutex()
    EndIf
    ProcedureReturn *mem
  EndProcedure
  
  Procedure Free(*this.udtCounter, Force = #False)
    With *this
      LockMutex(\mutex)
      If Force Or \counter = 0
        FreeMutex(\mutex)
        FreeStructure(*this)
        ProcedureReturn #True
      Else
        UnlockMutex(\mutex)
        ProcedureReturn #False
      EndIf
    EndWith
  EndProcedure
  
  Procedure Incr(*this.udtCounter)
    Protected r1
    With *this
      LockMutex(\mutex)
      \counter + 1
      r1 = \counter
      UnlockMutex(\mutex)
      ProcedureReturn r1
    EndWith
  EndProcedure
  
  Procedure Decr(*this.udtCounter)
    Protected r1
    With *this
      LockMutex(\mutex)
      If \counter
        \counter - 1
      EndIf
      r1 = \counter
      UnlockMutex(\mutex)
      ProcedureReturn r1
    EndWith
  EndProcedure
  
  Procedure Get(*this.udtCounter)
    Protected r1
    With *this
      LockMutex(\mutex)
      r1 = \counter
      UnlockMutex(\mutex)
      ProcedureReturn r1
    EndWith
  EndProcedure
  
  DataSection
    vTable:
    Data.i @Free()
    Data.i @Incr()
    Data.i @Decr()
    Data.i @Get()
  EndDataSection
  
EndModule

; ****

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

EnableExplicit

; ---- 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

; ****

; Threaded IPAddress$
; Threaded IP__$

Global cntThreads.Counter::iCounter = Counter::New()

Procedure DoIt(*x)
  Protected IPAddress$ = FreeString(*x)
  Debug "> " + IPAddress$
  cntThreads\Decr()
EndProcedure   

Procedure.s LookUp(IP$)
  cntThreads\Incr()
  If Not CreateThread(@DoIt(), AllocateString(IP$))
    cntThreads\Decr()
  EndIf
EndProcedure

Define i
For i = 1 To 20
  LookUp("8.8.8.8")
  LookUp("204.79.197.200")
  LookUp("162.125.65.13")
  Delay(50)
Next
While cntThreads\Get()
  Delay(100)
Wend

cntThreads\Free()
Last edited by mk-soft on Sat Feb 13, 2021 2:48 pm, edited 2 times in total.
My Projects ThreadToGUI / OOP-BaseClass / OOP-BaseClassDispatch / EventDesigner V3
PB v3.30 / v5.70 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace
User avatar
Bisonte
Addict
Addict
Posts: 1163
Joined: Tue Oct 09, 2007 2:15 am

Re: How to pass a string to a thread

Post by Bisonte »

If you use a thread more than once or twice, you should work with semaphores.

This is how I would do it :

Code: Select all

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

EnableExplicit

Structure ThreadData
  ThreadID.i
  Halt.i
  Work.i
  Counter.i
  List IP.s()
EndStructure

Global Thread.ThreadData
Global Signal_Thread = CreateSemaphore(0) ; We are at state 0 (no jobs)
Global Mutex_Thread  = CreateMutex()      ; to protect the list

; The Thread
Procedure.i DoIt(dummy)
  
  Protected IP.s = ""

  ; Set the state of working for the first time
  Thread\Work = #False ; Thread is not working
  Thread\Halt = #False ; Thread is not aborted by user
  
  Repeat
    
    ; Wait for a signal
    WaitSemaphore(Signal_Thread)
    
    ; Set working flag
    Thread\Work = #True
    
    ; Check if user want to abort
    If Thread\Halt = #True
      Debug "Abort It"
      Break ; jump after the ForEver statement (Closes Thread)
    EndIf
    
    ; Be sure that no datas are in the var
    IP = ""
    
    ; Lock the List in case of adding new "jobs"
    LockMutex(Mutex_Thread)
    
    ; Get the first element of the "joblist"
    If FirstElement(Thread\IP())
      IP = Thread\IP()
      DeleteElement(Thread\IP()) ; and delete it
    EndIf
    
    ; Unlock it
    UnlockMutex(Mutex_Thread)
    
    ; Show the result
    If IP <> ""
      Debug "IP - Lookup : " + IP
    EndIf
    Thread\Counter + 1
    ; Wait a little (if you dont want to flood a server with requests to get banned, maybe...)
    ; In this case of demo, it is to see some working
    Delay(500)
    
    ; Set the working state to false again
    Thread\Work = #False
    
  ForEver
  
  ; Here the thread will end so you can restart it if you want
  Thread\Halt     = #False
  Thread\Work     = #False
  Thread\ThreadID = #False
  
EndProcedure

; The Add Job Thing
Procedure.i AddIt(IP.s)
  
  ; Protect the List
  LockMutex(Mutex_Thread)
  
  ; Add your job
  AddElement(Thread\IP())
  Thread\IP() = IP
  Debug "Insert -> " + IP
  ; and unprotect it
  UnlockMutex(Mutex_Thread)
  
  ; Now send a signal to the thread
  SignalSemaphore(Signal_Thread)
  
EndProcedure

; The Stop Thread Thing
Procedure.i AbortIt()
  
  ; Set the Halt
  Thread\Halt = #True
  
  ; If the thread is staying at "WaitSemaphore()" send a signal to start a loop
  If Thread\Work = #False
    SignalSemaphore(Signal_Thread)
  EndIf
  
EndProcedure

; Demo 

Thread\ThreadID = CreateThread(@DoIt(), 0)

Define i, StopAfter
       
; StopAfter = ElapsedMilliseconds() + (30 * 600) ; We gave him 600ms time to do the programs job
StopAfter = ElapsedMilliseconds() + Random(30 * 200, 30 * 100) ; a shorter Random Time (we need 30x100 for the FOR:NEXT loop as a minimum)

; Add the jobs
For i = 1 To 30
  AddIt(Str(Random(255,0)) + "." + Str(Random(255,0)) + "." + Str(Random(255,0)) + "." + Str(Random(255,0)))
  Delay(100)
Next

; Now wait for the random time
While ElapsedMilliseconds() <= StopAfter
  Delay(1)  
Wend
; No more time or program end
AbortIt()

; Wait for program 
Delay(5000)
; And again Abort the thread, if the thread is working longer as expected
AbortIt()

Debug "We Inserted 30 Jobs and the thread do " + Str(Thread\Counter) + " of them"
Debug "Program ends"

End
Ok it is more to write (lazy programmers ;) ) but this will be more comfortable....
PureBasic 5.73 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
English is not my native language... (I often use DeepL to translate my texts.)
User avatar
TI-994A
Addict
Addict
Posts: 2490
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore

Re: How to pass a string to a thread

Post by TI-994A »

fluent wrote:...the string gets corrupted when being passed to the thread, and all 3 threads seem to be getting the same value...
Try this approach:

Code: Select all

Procedure DoIt(*x)
  IPAddress$ = PeekS(*x)   
  Debug "> " + IPAddress$ 
EndProcedure   

Procedure.s LookUp(*ip)
  CreateThread(@DoIt(), *ip)
  ProcedureReturn
EndProcedure

LookUp(@"8.8.8.8")
LookUp(@"204.79.197.200")
LookUp(@"162.125.65.13")

Delay(3000)
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too!
Post Reply