Page 2 of 2
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 10:46 am
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)
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 2:50 pm
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.
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()
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 3:26 pm
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.
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 4:27 pm
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.
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 5:21 pm
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
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 5:24 pm
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!
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 7:33 pm
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

Re: Using threads, passing data back
Posted: Mon Jan 02, 2023 8:41 pm
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?
Re: Using threads, passing data back
Posted: Tue Jan 03, 2023 3:11 am
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...
Re: Using threads, passing data back
Posted: Tue Jan 03, 2023 7:17 am
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

Re: Using threads, passing data back
Posted: Wed Jan 04, 2023 2:43 pm
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.
Re: Using threads, passing data back
Posted: Wed Jan 04, 2023 7:30 pm
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!