Page 1 of 2
Using threads, passing data back
Posted: Thu Dec 22, 2022 4:52 pm
by Oso
Hello all, I am trying to get to know the capabilities of threads, with the intention of implementing threading in existing code. In the documentation
https://www.purebasic.com/documentation ... hread.html it makes clear that passing values back is not supported. It confirms, "If you do try to return a value from your thread it will simply be lost". Do we have any other existing options for returning data?
From what I can see, passing parameters to a threaded procedure is done through a structure. In the sample code on the linked page, it shows a person's name, age and phone number being passed in that way. I just wanted to check if this is the only way, before I begin making changes.
Also what are the limits in terms of numbers of concurrent threads? Is this a function of the processor? In the below AMD specs, it refers to number of threads. I'm not sure if this is the same thing, although I know that cores refers to processors. Thanks for any information on this.
Code: Select all
AMD Ryzen 5 7600X AMD Ryzen 7 7700X AMD Ryzen 9 7900X
Cores 6, Threads 12 Cores 8, Threads 16 Cores 12, Threads 24
Re: Using threads, passing data back
Posted: Thu Dec 22, 2022 5:04 pm
by NicTheQuick
Your operating system keeps track of all the threads which are running currently on your system. There is an upper limit but you have to try hard to max it out. On my system for example there are currently running 3568 threads. It has nothing to to with the thread count a given processor is able to run in parallel. If a processor has a high thread count it just can run more threads in parallel at any time and such makes your system run faster.
If you want to be able to return values from a thread, you can use the structure which you are passing to the thread. I did something like this in the past but I can not find the topic for that again here in the forum. In other programming languages it often is called "joining threads" because after running concurrently for a while they join the main thread again. You can achieve something like this with WaitThread and a structure.
Re: Using threads, passing data back
Posted: Thu Dec 22, 2022 5:15 pm
by NicTheQuick
I just wrote something for you:
Code: Select all
Prototype.i ThreadProc(parameter.i)
Structure ThreadInfo
threadid.i
threadproc.ThreadProc
parameter.i
returnValue.i
EndStructure
Procedure WrapperThread(*threadInfo.ThreadInfo)
*threadInfo\returnValue = *threadInfo\threadproc(*threadInfo\parameter)
EndProcedure
Procedure.i JoinThread(*threadInfo.ThreadInfo)
WaitThread(*threadInfo\threadid)
ProcedureReturn *threadInfo\returnValue
EndProcedure
Procedure.i MyCreateThread(*threadProc, parameter.i)
Protected *threadInfo.ThreadInfo = AllocateStructure(ThreadInfo)
*threadInfo\parameter = parameter
*threadInfo\threadproc = *threadProc
*threadInfo\threadid = CreateThread(@WrapperThread(), *threadInfo)
ProcedureReturn *threadInfo
EndProcedure
; Here begins your program
Procedure mythread(parameter)
; Sum up all values up to parameter
Protected sum.i, i.i
For i = 1 To parameter
sum + i
; Make it run slower
Delay(1000)
Next
ProcedureReturn sum
EndProcedure
Debug "Start thread"
Define threadId.i = MyCreateThread(@mythread(), 3)
Debug JoinThread(threadId)
Hope that helps.
Re: Using threads, passing data back
Posted: Thu Dec 22, 2022 8:17 pm
by Oso
NicTheQuick wrote: Thu Dec 22, 2022 5:15 pm
I just wrote something for you:
Hope that helps.
Many thanks NicTheQuick, I appreciate the explanation about parallel CPU threads. I'm struggling to follow what the example does though (it's getting late here so perhaps in the morning I will follow it better

). Is the complexity of having those four procedures necessary in order to wait for the thread to finish? Also, do you need to free the structure? The example in the documentation frees the allocated memory inside the thread, but I realise of course we can't do that in this case because we're using it to pass data back.
I'm confused by the line
ProcedureReturn sum because the documentation suggests returned values are lost.
Re: Using threads, passing data back
Posted: Thu Dec 22, 2022 10:17 pm
by skywalk
Look up Semaphore. I use them to signal threads to pause when writing to a shared data structure.
infratec and mk-soft have excellent threading examples.
Re: Using threads, passing data back
Posted: Thu Dec 22, 2022 11:11 pm
by NicTheQuick
You are right. In the JoinThread procedure you should use FreeStructure() to deallocate the memory used for that construction.
> I'm confused by the line ProcedureReturn sum because the documentation suggests returned values are lost.
That works because the real thread is always WrapperThread() which catches that return value.
Re: Using threads, passing data back
Posted: Fri Dec 23, 2022 7:14 am
by Oso
Many thanks indeed for the replies NicTheQuick and skywalk, I think it's sensible that I first spend some time using threads and become familiar through trying it.
skywalk wrote: Thu Dec 22, 2022 10:17 pm
Look up Semaphore. I use them to signal threads to pause when writing to a shared data structure. infratec and mk-soft have excellent threading examples.
Yes, I saw mutex and locking when I started reading about threads and I guess semaphores is related to that. This looks ideal for what I need.
NicTheQuick wrote: Thu Dec 22, 2022 11:11 pm
That works because the real thread is always WrapperThread() which catches that return value.
Okay, got that NicTheQuick, thanks very much. I'm working on some simple examples to ease myself into this further.
Re: Using threads, passing data back
Posted: Fri Dec 23, 2022 9:25 am
by Marc56us
Hi Oso,
Read carefully what threads are in PB. It is not the same as in other languages.
Threads are useful for long operations. It is better to do as little as possible.
The more threads you use in a program, the more difficult it becomes to manage the whole thing in a safe way. You always end up forgetting to close some of them (fortunately closing the main program closes them all, but sometimes it forces the user to quit and restart the program)
See
Help PureBasic - Thread - Overview
A thread is a part of a program which runs asynchronously, in the background of this program. This means it's possible to perform long operations (compression, image processing, etc) without halting the whole program and let the user continue to do other things. A thread runs within your program, it's not another process. When the main program exits, all the threads are destroyed. Under PureBasic, threads are simply a procedure which is called asynchronously. The thread runs until the procedure exits.
Examples of places of programs where threads are useful are when you need to be able to handle multiple situations with different response times or which occur at different intervals. In the above paragraph, the response times of image processing and the user interface are quite different (you would want to wait for the image to be processed but always have the user interface to respond). [...]

Re: Using threads, passing data back
Posted: Fri Dec 23, 2022 1:24 pm
by skywalk
I disagree. I think everyone should be using threads and understand them.
When I see an app with an hourglass cursor I get annoyed. Much more professional to show a progress bar.
I will say that debugging a threaded app is more painful and the debugger sometimes gets lost in the main GUI event loop. Some breakpoints in the threads are missed. Stuff like that.
Re: Using threads, passing data back
Posted: Fri Dec 23, 2022 3:50 pm
by Oso
Thanks for the replies Marc56us and skywalk I'd hoped to look at this today but still behind with work and the holiday preparations too. We all have different requirements in regards to what we are developing. I feel that threads can become complex and it isn't always best to create a complex solution that becomes difficult for others to maintain. I come from a legacy background where there is no multithreading anyway, but more recently I've been working in C#, which has an asynchronous method. It is encouraged that you should hand over processing to a thread, but wait for it to complete, hence the below 'await'. This is done so that the event-driven UI on the main thread doesn't become unresponsive, which is your point I think, skywalk.
Code: Select all
static async Task Main(string[] args)
{
Egg eggs = await FryEggsAsync(2);
Console.WriteLine("eggs are ready");
Bacon bacon = await FryBaconAsync(3);
Console.WriteLine("bacon is ready");
}
In my case I'm writing server software, so it isn't a desktop application with an hourglass — nobody will see the work that the server process is doing, but they might find that the client end that makes requests to the server is sometimes slow, and it's for that reason I aim to use threading at the server side. At present I have a remarkably fast server process, but it serves only a single user's request at a time, so my objective is that when it receives a request, it instead hands that over to a thread and then continues to work for other incoming requests. So threads seem absolutely perfect for this (fingers crossed

)
Re: Using threads, passing data back
Posted: Fri Dec 23, 2022 9:11 pm
by mk-soft
Everything that belongs to serial or network communication should also be outsourced to threads and so that the GUI does not block or the GUI does not block the communication. Large pre-processing of data should also be outsourced to threads.
However, the transfer of data to the GUI should always take place in the main program, as this would otherwise lead to a crash under macOS and Linux and could also hang under Windows.
In order to send a message from the thread to the GUI main programme, there is an extra function called 'PostEvent'.
Reminder:
Mini Thread Control
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 7:27 am
by Oso
mk-soft wrote: Fri Dec 23, 2022 9:11 pm
Everything that belongs to serial or network communication should also be outsourced to threads and so that the GUI does not block or the GUI does not block the communication. Large pre-processing of data should also be outsourced to threads.
Thanks mk-soft it's beginning to fall into place now. I think it seems fairly logical which processes are candidates for threads. In particular, procedures that don't need to return any value (but just carry out a file update for example) are ideal.
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 7:38 am
by Oso
I found that I can pass values back from a thread using global variables (including global arrays). I don't expect this will be a revelation to others who use threads, because I'm sure you all know this already, but I just found it by chance.

In my example below, testglobal.s(10) is set to a new value and that element is available in the calling main process.
I wondered if the thread can access its own thread id. If the thread knows its own thread id., then it could pass a value via the appropriate array element — i.e. thread number 1 = element number 1. I don't know if there is a function to return the thread id.
inside a thread. Perhaps others will know the answer.
Even if that is not possible, it is easy to pass a sequential count parameter to the thread and let the thread set that particular array element.
Code: Select all
EnableExplicit
OpenConsole()
Global Dim testglobal.s(100)
Procedure Thread(*Dummy)
testglobal.s(10) = "New string set inside thread"
EndProcedure
Define *Dummy
Define threadid1 = CreateThread(@Thread(), *Dummy)
PrintN("Thread id. " + Str(threadid1) + " started")
PrintN("")
If threadid1
WaitThread(threadid1)
EndIf
PrintN("Thread has set testglobal.s(10) to : " + testglobal.s(10))
Print("Finished - Press Enter : ")
Input()
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 10:24 am
by Caronte3D
To be able to use global variables in threads you must use some lock system to read and write to it.
I ever use LockMutex() but you can use semaphores or your own lock method
Re: Using threads, passing data back
Posted: Sat Dec 24, 2022 10:39 am
by Oso
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.
I ever use LockMutex() but you can use semaphores or your own lock method
Ah! I thought it seemed to good to be true, Caronte3D

Thanks for mentioning that little caveat. Yeah, it makes sense though, presumably because another process could be changing the contents of the global variable.