Cromis.Threading
I have received quite a few mails recently, from people telling me, how I made their life easier using my code. I am really glad some of you find my code useful and easy enough to use. I made it public in case someone finds it useful.
I also made demo applications for most parts of the Cromis library, but one unit has almost no documentation and a lot of hidden content. This is the Cromis.Threading unit. Part of why this is so, is because this unit was made as a helper unit for Cromis.IPC. It contains the task (thread) pool that is used by Cromis.IPC. I needed my own lightweight implementation of a task pool so I wrote one. Then with time some other functionality regarding threading came into this unit. Mostly because I needed it here or there. But the side effects of this are that this functionality is not documented and probably not so easy to use for someone who is not very familiar with the code. The e-mails I got recently just prove that. So I decided I will quickly write a few examples of how to use the code and show all of the functions that this unit provides.
TTaskPool
This is the most obvious class that gives you control over a pool of tasks (threads). You start using it like this
procedure TfMain.btnStartClick(Sender: TObject); var Task: ITask; begin FTaskPool.DynamicSize := cbDynamicPoolSize.Checked; FTaskPool.MinPoolSize := StrToInt(ePoolSize.Text); FTaskPool.OnTaskMessage := OnTaskMessage; FTaskPool.Initialize; tmPoolStatus.Enabled := False; btnStart.Enabled := False; btnStop.Enabled := True; FTerminate := False; while not FTerminate do begin Task := FTaskPool.AcquireTask(OnTaskExecute, 'RandomTask'); Task.Values.Ensure('RandomNumber').AsInteger := Random(tbThreadTimeout.Position); Task.Run; pbPoolSize.Position := FTaskPool.PoolSize - FTaskPool.FreeTasks; stFreeThreadsValue.Caption := IntToStr(FTaskPool.FreeTasks); stPoolSizeValue.Caption := IntToStr(FTaskPool.PoolSize); Sleep(Random(tbCreationTimeout.Position)); Application.ProcessMessages; end; end; |
You have two important properties here that I will explain:
DynamicSize:
This boolean property controls if the size of the pool is dynamic. Let me explain. If you start with MinPoolSize of 20 and DynamicSize is FALSE then when all 20 threads are used, the pool will assign a new thread for each request it needs. So it will adjust to the peak load of the pool. But it will then stay at that peak number of threads. If your peak is at 60 it will stay there even if the load will then drop. But if DynamicSize is TRUE it will destroy unneeded threads until you again have the 20 (MinPoolSize) of threads. In other words it will dynamically adjust to the load. Each may have its uses.
MinPoolSize:
This one is simple. It is the number of threads you start with. You cannot have less then MinPoolSize of threads in the pool.
Ok now lets look at other parts of the workings of the pool. First is when the each task is executed:
procedure TfMain.OnTaskExecute(const Task: ITask); var Interval: Integer; begin Interval := Task.Values.Get('RandomNumber').AsInteger; try Task.Message.Ensure('Result').AsInteger := Interval; Sleep(Interval); finally Task.SendMessageAsync; end; end; |
And the second is processing the messages that tasks send back:
procedure TfMain.OnTaskMessage(const Msg: ITaskMessage); var Interval: Integer; begin Inc(FTaskCounter); Interval := Msg.Values.Get('Result').AsInteger; stThreadsFinishedValue.Caption := IntToStr(FTaskCounter); end; |
As you can see all is very straightforward. Before the task is run, you fill in the values of the task and then run it. You write the code for each task and each task can send back messages to the main thread. You can do that in two ways:
Task.SendMessageAsync;
Task.SendMessageSync;
Each one speaks for itself.
Let me be clear here. This is a simple implementation of the task pool build for my internal needs. Some find it usefull and that is great. But it is in no way comparable to the OmniThreadLibrary.
TThreadSafeQueue
This is a simple implementation of the thread safe Queue, that uses locking. It is very spartan and fast. Gabr wrote about it doing tests, some time ago:
http://www.thedelphigeek.com/2011/05/lock-free-vs-locking.html
http://www.thedelphigeek.com/2011/06/lock-free-vs-locking-rematch.html
The usage is very straightforward so no need to write about that.
TLockFreeStack
This class is a simple wrapper around the windows API and it enables the use of lock free stack.
ITaskQueue
This is a task queue that enables you to queue tasks even if the are run in multiple threads. This will ensure that your tasks will be executed in order that you want. The usage is very simple:
You create it like this:
FTaskQueue := AcquireTaskQueue; |
Then you enqueue
FTaskQueue.EnqueueTask.WaitFor; |
and dequeue
FTaskQueue.DequeueTask; |
destiny wrote,
Good programming feature for name pine communication between apps. It would be good if you could make a component for two way comm and multi-channel by combining server and client together.
Link | June 24th, 2012 at 1:30 am
Dave wrote,
Hello Iztok!
Does your implementation have any limits in the maximum concurrent workers? Can it have 100 tasks running at the same time?
Thanks
Link | December 11th, 2012 at 12:36 pm
Iztok Kacin wrote,
No. You set the limits, otherwise there are none. Ofc there are OS limits but that is a given
Link | December 11th, 2012 at 12:47 pm
Amir wrote,
Hi Iztok;
As I took a look at benchmarks provided by “DelphiGeek”, TAnyValue – which is used as data transfer element in your implementation – suffered from an issue and was replaced with TOmniValue due to your request for faster operations.
I’d like to know if TAnyValue implementation has improved to cover that issue or not ?
Thanks for ur great work.
Link | February 2nd, 2013 at 12:55 am
Iztok Kacin wrote,
@Amir
Yes it has changed. As I recall it should now be significantly faster. How fast I don’t know. I should do the benchmarks again. But is this really a problem. How many operation per second do you do to need such speed?
Link | February 2nd, 2013 at 10:10 am
Iztok Kacin wrote,
@Amir
I just reran the tests that were on my blog some time ago. TAnyValue is now faster in any way. By as much as 2x. I used the latest version of both TAnyValue and TOmniValue. I will make a blog post about it.
Link | February 2nd, 2013 at 11:22 am