Using threads, passing data back

Just starting out? Need help? Post your questions and find answers here.
User avatar
mk-soft
Always Here
Always Here
Posts: 6222
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Using threads, passing data back

Post by mk-soft »

Of course, the threads can return one or more values. As ByRef via the parameter.
I don't think you've looked at my four examples in Mini Thread Control. There's a lot in there that you want to know.

Or very simple ...

Code: Select all

; Simple thread example for result data ByRef

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

Structure udtThreadData
  ThreadID.i
  strVal.s
  fltVal.f
  List Text.s()
EndStructure

Procedure MyThread(*Data.udtThreadData)
  Protected index
  With *Data
    \strVal = "Hello World!"
    \fltVal = 2023.24
    For index = 1 To 10
      AddElement(\Text())
      \Text() = "List Entry " + index
      Delay(100)
    Next
  EndWith
  
EndProcedure

Global *MyData.udtThreadData

*MyData = AllocateStructure(udtThreadData)
With *MyData
  \ThreadID = CreateThread(@MyThread(), *MyData)
  While IsThread(\ThreadID)
    Debug "Wait ..."
    Delay(100)
  Wend
  ;WaitThread(\ThreadID)
  Debug \strVal
  Debug \fltVal
  ForEach \Text()
    Debug \Text()
  Next
EndWith

FreeStructure(*MyData)
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Using threads, passing data back

Post by Oso »

mk-soft wrote: Sat Dec 24, 2022 10:46 am Of course, the threads can return one or more values. As ByRef via the parameter.
I don't think you've looked at my four examples in Mini Thread Control. There's a lot in there that you want to know.
I've looked at them but there's an awful lot for me to absorb at this early stage in my learning about threads. I'm still really new to this. A lot of it is still a challenge to follow. :D

Here's something that's just thrown me off course for example. I was trying the use structures to define thread parameters, but since my two threads have different functions, the parameters are slightly different. I thought I could use the same structure name 'Params' in both procedures and that the structures would remain local to each procedure but the compiler won't allow it. I can rename them uniquely, but I wasn't expecting that (bear in mind the below is a very cut-down version of the more extensive code).

Code: Select all

Procedure test_1()
  Structure Params
    p1.s
  EndStructure
EndProcedure

Procedure test_2()
  Structure Params
    p1.s
    p2.i
  EndStructure
EndProcedure

test_1()
test_2()
User avatar
NicTheQuick
Addict
Addict
Posts: 1510
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: Using threads, passing data back

Post by NicTheQuick »

How would you even distinguish them outside the procedures? At this point you could use Macros or tell us a bit more about what you wanna achieve. I can not give you new codes at this time because I am not at my computer.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
User avatar
mk-soft
Always Here
Always Here
Posts: 6222
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Using threads, passing data back

Post by mk-soft »

Structures always global ... ? !!! (except modules)

You do not define any structures within a procedure, even if the compiler doesn't complain about them.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
the.weavster
Addict
Addict
Posts: 1577
Joined: Thu Jul 03, 2003 6:53 pm
Location: England

Re: Using threads, passing data back

Post by the.weavster »

Another option is using a Map of Structures with an integer converted to a string as the key.
I think it's best to add/delete map elements in the main thread, I find it's easier to keep track that way:

Code: Select all

EnableExplicit

Enumeration windows
  #frmMain
EndEnumeration

Enumeration gadgets
  #frmMain_btnThreads
EndEnumeration

Enumeration #PB_Event_FirstCustomValue
  #EventThreadComplete
EndEnumeration

Structure AddIt
  a.i
  b.i
  x.i
EndStructure

Global MapIndex.i
Global NewMap MyMap.AddIt()
Global Mutex = CreateMutex()

; work that happens in a thread
Procedure ThreadStuff(nMapId)
  Define.i nA, nB, nX
  ; grab any incoming data we need for our work ...
  LockMutex(Mutex)
  If FindMapElement(MyMap(), Str(nMapId))
    nA = MyMap()\a
    nB = MyMap()\b
  EndIf
  UnlockMutex(Mutex)
  
  ; perform the intensive, time-consuming work ...
  nX = nA + nB
  
  ; record the outcome ...
  LockMutex(Mutex)
  If FindMapElement(MyMap(), Str(nMapId))
    MyMap()\x = nX
  EndIf
  UnlockMutex(Mutex)
  
  ; announce thread has completed its task ...
  ; the map id is attached to the event as its data ...
  PostEvent(#EventThreadComplete, 0, 0, 0, nMapId)
EndProcedure

; PostEvent() handler is back in the main thread ...
Procedure onPostEvent()
  ; EventData() tells us which map element is broadcasting ...
  Protected nIdx.i = EventData()
  Protected sResult.s
  LockMutex(Mutex)
  If FindMapElement(MyMap(), Str(nIdx))
    ; read the result of the thread's work ...
    sResult = Str(MyMap()\a) + " + " + Str(MyMap()\b)  + " = " + Str(MyMap()\x)
    Debug sResult
    ; we've handled this element so we can clean up ...
    DeleteMapElement(MyMap(), Str(nIdx))
  EndIf
  UnlockMutex(Mutex) 
EndProcedure

; gadget event handlers
Procedure onGadget()
  Protected nGadget = EventGadget()
  If nGadget = #frmMain_btnThreads
    ; launch a new task in a thread ...
    MapIndex + 1
    LockMutex(Mutex)
    AddMapElement(MyMap(), Str(MapIndex))
    MyMap()\a = Random(100, 0)
    MyMap()\b = Random(100, 0)
    UnlockMutex(Mutex)
    CreateThread(@ThreadStuff(), MapIndex)
  EndIf
EndProcedure

Procedure frmMain_Open()
  If OpenWindow(#frmMain, 20, 20, 400, 300, "Threads Example")
    ButtonGadget(#frmMain_btnThreads, 10, 10, 150, 30, "Test")
  EndIf  
EndProcedure

BindEvent(#PB_Event_Gadget, @onGadget())
BindEvent(#EventThreadComplete, @onPostEvent())
frmMain_Open()

Repeat
  Define.i nEvent = WaitWindowEvent(50)
Until nEvent = #PB_Event_CloseWindow
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Using threads, passing data back

Post by Oso »

mk-soft wrote: Sat Dec 24, 2022 4:27 pm Structures always global ... ? !!! (except modules)
You do not define any structures within a procedure, even if the compiler doesn't complain about them.
Got it, thanks mk-soft, I thought that was probably the reason, but I couldn't find anything in the docs to say that structures are globals!
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Using threads, passing data back

Post by Oso »

mk-soft wrote: Sat Dec 24, 2022 10:46 am Or very simple ...

Code: Select all

Procedure MyThread(*Data.udtThreadData)
  Protected index
  With *Data
    \strVal = "Hello World!"
    \fltVal = 2023.24
    For index = 1 To 10
      AddElement(\Text())
      \Text() = "List Entry " + index
This is much easier to follow, I can see what it's doing. Thanks for putting that together :)
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Using threads, passing data back

Post by Oso »

the.weavster wrote: Sat Dec 24, 2022 5:21 pm Another option is using a Map of Structures with an integer converted to a string as the key.

Code: Select all

Procedure ThreadStuff(nMapId)
  Define.i nA, nB, nX
  ; grab any incoming data we need for our work ...
  LockMutex(Mutex)
  If FindMapElement(MyMap(), Str(nMapId))
    nA = MyMap()\a
    nB = MyMap()\b
  EndIf
  UnlockMutex(Mutex)
[snipped]  
Thanks @the.weavster for this example code a few days ago and apologies I didn't drop a line back yet. I appreciate the example. Incidentally, thanks to all others who helped me with this, if I haven't mentioned previously. This is interesting though — the idea of passing values to and from the thread through the map, with the index as a pointer to the element within the map where the thread can find its values.

I noticed that you're locking the mutex in order to read the map (not just to write back to the map). This corresponds with Caronte3D's comment about locking. I wasn't sure at the time why it was necessary to lock the mutex in order to read a global variable...
Caronte3D wrote: Sat Dec 24, 2022 10:24 am To be able to use global variables in threads you must use some lock system to read and write to it.
What is the reason for this? I note that in the same code, you're referencing the global variable "Mutex" so presumably that's okay in that case? Therefore what are the rules for needing to provide a lock, when accessing a global?
Rinzwind
Enthusiast
Enthusiast
Posts: 679
Joined: Wed Mar 11, 2009 4:06 pm
Location: NL

Re: Using threads, passing data back

Post by Rinzwind »

Because of a weird design decision to make the map's current element pointer not "threaded". It is shared across threads and so reads are not safe (can point to another element than expected). Should be relatively easy to fix the internals to allow read access from multiple threads, but alas...
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Using threads, passing data back

Post by Oso »

Rinzwind wrote: Tue Jan 03, 2023 3:11 am Because of a weird design decision to make the map's current element pointer not "threaded". It is shared across threads and so reads are not safe (can point to another element than expected). Should be relatively easy to fix the internals to allow read access from multiple threads, but alas...
Thanks very much Rinzwind I hadn't thought of that, so it's a question of the current element position, rather than the map's own data being the factor here (and lists too). This seems obvious to me now, with your explanation :D
User avatar
Caronte3D
Addict
Addict
Posts: 1359
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: Using threads, passing data back

Post by Caronte3D »

Oso wrote: Mon Jan 02, 2023 8:41 pm What is the reason for this? I note that in the same code, you're referencing the global variable "Mutex" so presumably that's okay in that case? Therefore what are the rules for needing to provide a lock, when accessing a global?
Use Global for the mutex variable is ok.

The rule is (I think):
You can't read or write a global variable at same time of another thread, so if you have paralel code, it's a *must* to ensure only one access is done each time.
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Using threads, passing data back

Post by Oso »

Caronte3D wrote: Wed Jan 04, 2023 2:43 pm
Oso wrote: Mon Jan 02, 2023 8:41 pm What is the reason for this? I note that in the same code, you're referencing the global variable "Mutex" so presumably that's okay in that case? Therefore what are the rules for needing to provide a lock, when accessing a global?
Use Global for the mutex variable is ok. The rule is (I think): You can't read or write a global variable at same time of another thread, so if you have paralel code, it's a *must* to ensure only one access is done each time.
Interesting, thanks for posting back on this. I've become rather cautions about using globals anyway during the past few days. I'm rewriting my code with threads now and I'm avoiding using globals. Now that I'm a bit more proficient/confident :) with using structured pointers, I'm happier to pass everything needed into the thread in that way!
Post Reply