Delphi 10: Correct way to run tasks simultaneously - multithreading

I'm trying to learn how to use delphi parallel library instead of TThread. I have many tasks that I want to run simultaneously. Each task is most time waiting for some event, while its waiting, I'd like to pass excecution to another tasks.
Here is my sample code:
type
TObj = class
pos: integer;
Done: boolean;
Constructor Create;
procedure DoWork(Sender: TObject);
end;
var
Objects : array [0..39] of TObj;
tasks : array of ITask;
constructor TObj.Create;
begin
pos:=0;
DOne:=false;
end;
procedure TObj.DoWork(Sender: TObject);
begin
repeat
inc(pos);
sleep(100);
until Done;
end;
procedure TForm1.StartClick(Sender: TObject);
var
i: integer;
begin
Setlength (tasks ,Length(Objects));
for i:=0 to Length(tasks)-1 do begin
Objects[i]:=TObj.Create;
tasks[i] := TTask.Create(Objects[i],Objects[i].DoWork);
tasks[i].Start;
end;
sleep(5000);
Memo1.Lines.Clear;
for i:=0 to Length(tasks)-1 do begin
Objects[i].Done:=true;
Memo1.Lines.Add(IntToStr(Objects[i].pos));
end;
end;
I start N Tasks, each should increase its counter by 1 in 100ms. Then I wait 5 sec and check task's values. I excpect them to be at least nearly equal, but real Memo1's output shows first 12 values are about 50, the other 4 values about 20-30 (I'm running Ryzen 1600), and whats strange fo me that the least values are all 0!
Evidently only 16 tasks from 40 actually excecuted atleast once in 5 seconds, so I would like to know how I can replace sleep(100) to actually pass excecution to another tasks?

Just because you start N number of tasks does not mean that N number of tasks will be running concurrently. If you want that, stick with TThread.
The PPL uses an internal thread pool to service the TTask objects, and that pool is self-throttling and self-adjusting based on now many CPUs you have installed and how many tasks are actually running. This is explained in the PPL documentation:
The RTL provides the Parallel Programming Library (PPL), giving your applications the ability to have tasks running in parallel taking advantage of working across multiple CPU devices and computers. The PPL includes a number of advanced features for running tasks, joining tasks, waiting on groups of tasks, etc. to process. For all this, there is a thread pool that self tunes itself automatically (based on the load on the CPU’s) so you do not have to care about creating or managing threads for this purpose.
If you create more tasks than the pool has threads, some tasks are going to be waiting for earlier tasks to finish their work. When a given thread finishes a task, it checks for a waiting task, and if found then runs it, repeating until there are no more tasks to run. Multiply that by the number of threads in the pool and the number of tasks in the queue.
So, there will only ever be a handful of tasks running at any given time, and so it may take awhile to get through all of the tasks that you queue up.
If you want more control over the thread pooling, you have to create a TThreadPool object, set its MinWorkerThreads and MaxWorkerThreads properties as desired, and then pass that object to one of the TTask constructors that has an APool input parameter. By default, the MinWorkerThreads is set to TThread.ProcessorCount, and the MaxWorkerThreads is set to TThread.ProcessorCount * 25.

Related

Delphi: How I change the ITask's priority?

I have some low-priority tasks that are working in background, I dont care about how fast they are executed, but sometimes there is a single task that must be executed as fast as possible. How i do that?
for k:=1 to 100 do begin
Orders[k] := TTask.Create(NewOrder,NewOrder.DoTheJob);
Orders[k].Start;
end;
......
FastOrder := TTask.Create(NewOrder,NewOrder.DoTheJob);
FastOrder.Start; // I want this task to be executed ASAP,
//but actually it waits unpredictable time until there is a free slot
// in task's queue or so.
//It can delay up to 1 minute depending on how many reqular tasks i have
Use TThread.createAnonymousThread instead of TTask

Threads and Critical Section correct approach

Right now have a multi-thread scheme like this:
//global variables
var
Form1: TForm1;
ControlFile: TextFile;
MaxThreads, iThreads: integer;
MyCritical: TCriticalSection;
The ControlFile is accessed by the threads, that do a ReadLn, and perform actions with the line obtained:
procedure TForm1.Button2Click(Sender: TObject);
var
HostLine: AnsiString;
FileHandle: integer;
begin
MyCritical:= TCriticalSection.Create;
MaxThreads:= 100;
iThreads:= 0;
while not(eof(ControlFile)) and (iThreads < MaxThreads) do
begin
inc(iThreads);
ReadLn(ControlFile, HostLine);
MyThread.Create(HostLine);
end;
end;
this block is the first doubt. I'm creating 100 threads, each one created received the current line of the textfile. But the problem is that on threads.onterminate, I execute this:
procedure MyThread.MainControl(Sender: TObject);
var
HostLine: string;
begin
try
MyCritical.Acquire;
dec(iThreads);
while not(eof(ControlFile)) and (iThreads < MaxThreads) do
begin
inc(iThreads);
ReadLn(ControlFile, HostLine);
MyThread.Create(HostLine);
end;
finally
MyCritical.Release;
end;
end;
The idea is to keep creating new threads, until the textfile finishes. But if one thread terminate, and execute this procedure, before the first while finished, what happens? The main thread, from button2click will be accessing the file, and the thread's procedure too. This looks strange to me. And the Critical Section, should be global or thread local? And this procedure, MainControl, that opens new threads until the end of the file, should be global or thread local?
First of all, I am not sure it's such a stellar idea to have different threads read from the same text file. It's not that it can't work, but I think it would be much cleaner to simply read the whole thing into a TStringList variable up front, which can then be shared among threads, if needed.
If you do go with what you already have, your critical section must be acquired in the main loop also - the threads that you spawn will start executing immediately by default, so it looks like there could be a race between your main thread and the threads that run MainControl, though you don't show exactly how that call is going to be made.
The critical section needs to be a global variable, as you have it, or a field/property of a global class in order to be shared among threads.
My final point is that it's probably not the greatest idea to create 100 threads either. Unless your threads are mostly waiting on I/O or events, you should generally not have more threads than you have CPU cores. It's better to use a pool of worker threads and a queue of work items that can then be doled out to the running threads. There's supposedly built-in support for some of this in more recent Delphi RTLs. I personally use my own tried and true custom thread pool implementation, so I can't give you any specific help with that part.
The call to OnTerminate is already synchronised.
procedure TThread.DoTerminate;
begin
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;
So the critical section is not actually needed at all because all the code you've shown runs in the context of the main thread.

Delphi not all threads executing simultaneously

I have the following scenario:
The program I am writing simulates multiple air networks. Now I have written it so that each simulation is in its own thread with its own data. This is so that there can be no contamination of data and each thread is complete multitasking safe. I have implemented delphi's native TThread class for each thread.
The program has one main thread. It then creates multiple sub threads for each network (my current test case has 6). But each sub thread also has 4 additional threads since its network has more than one layout. So in total I have 31 threads, but only 24 threads actively processing. All threads are created in suspended mode so that I can manually start them.
The computer I am running on is an i5 laptop so it has 4 threads (2 physical cores + 2 HT). In the production environment this will run on servers so they will have more processing power.
Now in the main program thread I have added a break point after the for loop which executes the threads. This doesn't trigger immediately. Watching the debug console of Delphi, it looks like it is only actively running 4 threads at a time. As soon as one exits, it starts another thread.
The simulations in the threads are difficult to optimize and require a few loops to finish the simulation. And each loop requires the previous loop's data. But each simulation is completely independent of the next so the program is an excellent candidate for threads and pipe lining.
Now my question is why does it only start 4 threads and not all the threads as the code specifies?
EDIT Added pieces of code
Main program
for i := 0 to length(Solutions)-1 do
begin
Solutions[i].CreateWorkers; //Setup threads
end;
for i := 0 to length(Solutions)-1 do
begin
Solutions[i].Execute; //start threads
end;
end;
isSolversBusy := false; //breaking point doesn't trigger here
1St level of threads
procedure cSolution.Execute;
var
i : integer;
lIsWorkerStillBusy : boolean;
begin
lIsWorkerStillBusy := true;
for i := 0 to length(Workers)-1 do
begin
Workers[i].Start;
end;
while (lIsWorkerStillBusy) do
begin
lIsWorkerStillBusy := false;
for i := 0 to length(Workers)-1 do
begin
if Workers[i].IsCalculated = false then
begin
lIsWorkerStillBusy := true;
end;
end;
sleep(100);
end;
FindBestNetwork;
IsAllWorkersDone := true;
end;
2nd level of threads
procedure cWorker.Execute;
begin
IsCalculated := false;
Network.UpdateFlows;
Network.SolveNetWork; //main simulation work
CalculateTotalPower;
IsCalculated := true;
end;
EDIT 2
The reason I create them all suspended is because I store them in an array and before I start them I first have create the workers and their properties. I am simulating air network scenarios. Each solution is a different layout, while each worker is a different way of running that layout.
I have to first calculate all the worker's start properties before I start them all. In hind sight I could modify the code to do that in the thread. It currently happens in the main thread. The creation of the threads happens before the piece of code I pasted here.
The reason I keep them all in threads is that I need to evaluate the results of each thread afterwords.
This is your problem :
for i := 0 to length(Solutions)-1 do
begin
Solutions[i].Execute; //start threads
end;
This does not start the threads - this is executing the Execute method in the calling thread. In fact you were not running only 4 threads at a time, you were not even running one - all of this work would be done on the main thread sequentially. To resume a suspended thread you must use Solutions[i].Start.
The Execute method of a TThread is a special method that is executed on the worker thread created by the TThread automatically. When you create a TThread this method is automatically run on the worker thread that the TThread creates. If you create the thread suspended then it simply waits for you to wake the thread before beginning this work. Calling the .Start method of a TThread is what accomplishes this - triggering the underlying worker thread to begin executing the .Execute method.
Otherwise, the Execute method, and all other methods of a TThread are no different from any other normal method belonging to a class. They can be executed on any thread that calls them directly.
In this case, it doesn't seem like you are doing any additional work in the main thread between creating and executing your workers. In this case, unless you have an explicit need for it, you could simply create your threads not-suspended and let them execute automatically upon creation.

Thread Pool Class developement [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I run a multithread application and I want to limit the number of threads on my machine
The code concept goes currently like this (it is just a drft to show the major ideas behind)
// a List with all the threads I have
class MyTHreadList = List<TMyCalcThread>;
// a pool class, check how many threads are active
// try to start additions threads once the nr. of running THreads
// is below the max_thread_value_running
class MyTheardPool = Class
ThreadList : MyTHreadList;
StillRunningTHreads : Integer;
StartFromThreadID : Integer;
end;
var aTHreadList : MyTHreadList;
procedure MainForm.CallCreateThreadFunction( < THREAD PARAMS > );
begin
// just create but do not start here
MyTHread := TMyCalcThread.create ( < THREAD PARAMS > );
MyTHread.Onterminate := What_To_do;
// add all threads to a list
aTHreadList.add(MyTHread);
end;
/// (A)
procedure MainForm.What_To_do ()
var start : Integer;
begin
max_thread_value_running := max_thread_value_running -1;
if max_thread_value_running < max_thread_count then
begin
start := max_thread_count - max_thread_value_running;
startThereads(start,StartFromThreadID)
end;
end;
procedure MainForm.startThereads (HowMany , FromIndex : Integer);
var i : INteger;
begin
for I := FromIndex to HowMany + FromIndexdo
begin
// thread[i].start
end;
end;
MainForm.Button ();
/// main VCL form , create threats
for i := 0 to allTHreads do
begin
CallCreateTHreadFunction( < THREAD PARAMS > );
end;
......
/// (B)
while StillRunningTHreads > 0 do
begin
Application.processmessages;
end;
The complete idea is a small list with the Threads, on every individual Thread terminate step I update the number of running threads and start the now possible max. number of new threads.(A) Instead of a Function WaitforSingleObject () .... I do a loop at the end to wait for all threads to finish execution. (B)
From the code design I did not find any full example on the net, I may approach a vaild design or will I run into some trouble which I did not consider right now.
Any comment on the diesign or a better class design is welcome.
Don't try to micro-manage threads like this. Just don't. Either use the Win API threadpool calls, or make your own threadpool from a producer-consumer queue and TThread instances.
When a task is completed, I suggest that the work thread call an 'OnCompletion' TNotifyEvent of the task class with the task as the parameter. Ths can be set by the issuer of the task to anything they might wish, eg. postMessaging the task instance to the GUI thread for display.
Micro-managing threads, continually creating/waiting/terminating/destroying, Application.processmessages loops etc. is just horrible and almost sure to go wrong at some point.
Waiting for ANYTHING in a GUI event-handler is just bad. The GUI system is a state-machine and you should not wait inside it. If you want to issue a task from the GUI thread, do so but don't wait for it - fire it off and forget it until it is completed and gets posted back to a message-handler procedure.
Basically thread-pools are a nice feature (There is an implementation in the Win32 API, however I don't have any experience with it).
There is one basic stumble stone however: You need to remember that a task may be delayed until an empty thread is available. If you need synchronization between different tasks (e.g. tasks are waiting on other tasks) then you have a serious deadlock problem:
Just assume that all running tasks wait for a single task which is waiting for a free thread...
A similar problem can also happen if your threads wait for the main thread to react while the main thread waits for a new task to start.
If your tasks don't need any further synchronization (e.g. once a task is finished it will just mark itself as finished and the main thread will then later on read the result) you don't need to worry about this.
As a small side note:
I would use two separate lists: One for free (suspended) threads and one for running threads.
I'd consider to use an existing Thread-Pool implementation (like Winapi CreateThreadpool) before I created my own...
The loop (B) takes away a lot of CPU power from the threads. Don't wait actively in a loop for threads, use one of the WaitFor.... functions.
Reuse your threads and have a list of "todos" that the threads will execute. Thread creation and destruction can be expensive.
Apart from that I'd recommend that you use an existing library instead of reinventing the wheel. Or use at least the Windows API thread pool functions.

OmniThreadLibrary: How to detect when all recursively scheduled (=pooled) threads have completed?

Let's say I have to recursively iterate over items stored in a tree structure in the background and I want to walk this tree using multiple threads from a thread pool (one thread per "folder" node). I have already managed to implement this using several different low and high-level approaches provided by the OmniThreadLibrary.
However, what I haven't figured out yet is how to properly detect that the scan has actually completed, i.e. that every last leaf node has been processed.
I found various examples on the net that either checked whether GlobalThreadPool.CountExecuting + GlobalThreadPool.CountQueued <= 0 or that used a IOmniTaskGroup.WaitForAll(). Unfortunately, none of these approaches appears to work for me. The check always returns True too early, i.e. when there still are some tasks running. None of the examples I looked at used recursion though - and those that did did not use a thread pool - is this maybe just not a good combination in the first place?
Here's a (very) simplified example code snippet of how I'm trying to do this at the moment:
procedure CreateScanFolderTask(const AFolder: IFolder);
begin
CreateTask(ScanFolder)
.SetParameter('Folder', AFolder)
.Schedule();
end;
procedure ScanFolder(const ATask: IOmniTask);
var
lFolder,
lCurrentFolder: IFolder;
begin
if ATaks.CancellationToken.IsSignalled then Exit;
lCurrentFolder := ATask.Param['Folder'].AsInterface as IFolder;
DoSomethingWithItemsInFolder(lCurrentFolder.Items);
for lFolder in lCurrentFolder.Folders do
begin
if ATaks.CancellationToken.IsSignalled then Exit;
CreateScanFolderTask(lFolder);
end;
end;
begin
GlobalOmniThreadPool.MaxExecuting := 8;
CreateScanFolderTask(FRootFolder);
// ??? wait for recursive scan to finish
OutputResult();
end.
One example implementation for the wait that I have tried was this (based on an example found on About.com):
while GlobalOmniThreadPool.CountExecuting + GlobalOmniThreadPool.CountQueued > 0 do
Application.ProcessMessages;
But this appears to always exit immediately right after the "root thread" has finished. Even when I add an artificial delay using Sleep()-calls it still always exits too early. It seems that there occurs a "gap" between one task being struck off the list of executing tasks and the ones that were scheduled inside that task to be added to the list of queued tasks...
Actually, instead of waiting for the scan to finish, I would very much prefer to use an event handler (also, I'd rather not use Application.ProcessMessages as I will need this in form-less applications, too) and I already did try with both IOmniTaskControl.OnTerminated and using a TOmniEventMonitor but as these fire for every finished task I still somehow need to check whether the current one was the last one which again boils down to the same problem as above.
Or is there maybe a better way to create the tasks that would avoid this problem?
A simple way is to count 'folders to be processed' by yourself. Increment a value every
time you create a folder task and decrement it every time a folder is processed.
var
counter: TOmniCounter;
counter.Value := 0;
procedure ScanFolder(const ATask: IOmniTask);
var
lFolder,
lCurrentFolder: IFolder;
begin
if ATaks.CancellationToken.IsSignalled then Exit;
lCurrentFolder := ATask.Param['Folder'].AsInterface as IFolder;
DoSomethingWithItemsInFolder(lCurrentFolder.Items);
for lFolder in lCurrentFolder.Folders do
begin
if ATaks.CancellationToken.IsSignalled then Exit;
counter.Increment;
CreateScanFolderTask(lFolder);
end;
counter.Decrement;
end;
What I usually do is to count all the issued 'folderScan' objects out and count them back in again.
Each time a new TfolderScan is needed, the creating TfolderScan calls a factory for it The factory increments a CS-protected 'taskCount' as well as creating the TfolderScan. Every time a TfolderScan is completed, it calls the 'OnComplete' method of the factory that decrements the CS-protected 'taskCount'. If, in 'OnComplete', the count is decremeted to 0, there can be no TfolderScan left and the whole search most be complete. The thread that manages to decrement the count to 0 can do whatever is needed to signal the completion - PostMessage() a main form or call an 'OnSearchComplete' event.
Just as a side-note : Instead of checking (GlobalOmniThreadPool.CountExecuting + GlobalOmniThreadPool.CountQueued > 0) use (not GlobalOmniThreadPool.IsIdle). This hides implementation details and is more efficient.

Resources