I have a time consuming routine which I'd like to process in parallel using Delphi XE7's new parallel library.
Here is the single threaded version:
procedure TTerritoryList.SetUpdating(const Value: boolean);
var
i, n: Integer;
begin
if (fUpdating <> Value) or not Value then
begin
fUpdating := Value;
for i := 0 to Count - 1 do
begin
Territory[i].Updating := Value; // <<<<<< Time consuming routine
if assigned(fOnCreateShapesProgress) then
fOnCreateShapesProgress(Self, 'Reconfiguring ' + Territory[i].Name, i / (Count - 1));
end;
end;
end;
There is really nothing complex going on. If the territory list variable is changed or set to false then the routine loops around all the sales territories and recreates the territory border (which is the time consuming task).
So here is my attempt to make it parallel:
procedure TTerritoryList.SetUpdating(const Value: boolean);
var
i, n: Integer;
begin
if (fUpdating <> Value) or not Value then
begin
fUpdating := Value;
n := Count;
i := 0;
TParallel.For(0, Count - 1,
procedure(Index: integer)
begin
Territory[Index].Updating := fUpdating; // <<<<<< Time consuming routine
TInterlocked.Increment(i);
TThread.Queue(TThread.CurrentThread,
procedure
begin
if assigned(fOnCreateShapesProgress) then
fOnCreateShapesProgress(nil, 'Reconfiguring ', i / n);
end);
end
);
end;
end;
I've replaced the for-loop with a parallel for-loop. The counter, 'i' is locked as it is incremented to show progress. I then wrap the OnCreateShapeProgress event in a TThread.Queue, which will be handled by the main thread. The OnCreateShapeProgress event is handled by a routine which updates the progress bar and label describing the task.
The routine works if I exclude the call to the OnCreateShapeProgress event. It crashes with an EAurgumentOutOfRange error.
So my question is simple:
Am I doing anything anything stupid?
How to do you call an event handler from within a TParallel.For loop or TTask?
The most obvious problem that I can see is that you queue to the worker thread.
Your call to TThread.Queue passes TThread.CurrentThread. That is the very thread on which you are calling TThread.Queue. I think it is safe to say that you should never pass TThread.CurrentThread to TThread.Queue.
Instead, remove that parameter. Use the one parameter overload that just accepts a thread procedure.
Otherwise I'd note that the incrementing of the progress counter i is not really handled correctly. Well, the incrementing is fine, but you then read it later and that's a race. You can report progress out of order if thread 1 increments before thread 2 but thread 2 queues progress before thread 1. Solve that by moving the counter increment code to the main thread. Simply increment it inside the queued anonymous method. Added bonus to that is you no longer need to use an atomic increment since all modifications are on the main thread.
Beyond that, this QC report seems rather similar to what you report: http://qc.embarcadero.com/wc/qcmain.aspx?d=128392
Finally, AtomicIncrement is the idiomatic way to perform lock free incrementing in the latest versions of Delphi.
Related
I'm trying to manipulate a variable of type array of record, by multiple threads, and I'm not sure whether what i did is the right way, or if there is a better and safer method?
I declared a boolean variable as a lock, and when some thread want to access the array it waits till the lock is off, then activate the lock, and when done, unlock it and let others have access.
The code for this is declared in the implementation section
...
implementation
var Data : array of TData;
var Data_Lock:Boolean=false;
procedure Lock_Data();
begin
while Data_Lock = True do
sleep(1);
Data_Lock := True;
end;
procedure UnLock_Data();
begin
Data_Lock := False;
end;
procedure ClearAll();
begin
Lock_Data();
SetLength( Data, 0 );
UnLock_Data();
end;
....
The entire project is still not complete. For now this seems to work but i don't have any knowledge of how these things work at the core, and whether there would be a problem if two threads start at the exact same time?
Your locking approach is not thread safe and it will not protect your data.
With multiple threads you have to consider that any particular thread execution can be interrupted at any time and that another thread can "jump" in and access some variable in between.
That means following scenario is possible (simplified):
Data_Lock is False
Thread A enters Lock_Data()
Thread A checks Data_Lock -> False and skips the loop
Thread B enters Lock_Data()
Thread B checks Data_Lock -> False and skips the loop (Thread A didn't have the chance to set it to True yet)
Thread A continues -> sets Data_Lock to True and gains access to protected data
Thread B continues -> sets Data_lock to True and gains access to protected data while Thread A is still using that data
You can use TCriticalSection from System.SyncObjs instead.
var DataLock: TCriticalSection;
procedure ClearAll();
begin
DataLock.Enter;
try
SetLength(Data, 0);
finally
DataLock.Leave;
end;
end;
Since TCriticalSection is a class you need to create DataLock instance before you can use it and you need to free it when you no longer need it. For instance, you can do that in initialization/finalization section of a unit.
initialization
DataLock := TCriticalSection.Create;
finalization
DataLock.Free;
end.
However, the better approach would be to wrap your data and critical section together in a class.
I am new to parallel programming and am trying to find out why I occasionally get an EmonitorLockException:Object lock not owned when I increase the number of parrallel tasks to run. Is the case that the Threads become tangled the more tasks I run. Or is my code code not correct?
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.Threading, System.Classes, System.SyncObjs, System.StrUtils;
const
WorkerCount = 10000; // this is the number of tasks to run in parallel note:when this number is increased by repeated factors of 10
// it takes longer to produce the result and sometimes the program crashes
// with an EmonitorLockException:Object lock not owned . Is it that my program is not written correctly or efficiently to run
// a large number of parallel taks and the Threads become entagled.
// Eventually I would like to optimeize the program to find the optimal number of tasks to run in parallel so as to find the result in the shortest time.
// This becomes important when the word sequence is increased to six or more letters.
sequencetofind='help'; // letter sequence to find randomly
sequencelengthplus1=5; // the ength of the letter sequence plus 1 extra letter for a check to see if it is working
var
Ticks: Cardinal;
i,k,m: Integer;
sequencenum: Integer;
alphabetarray:array[1..26] of string;
v:char;
copyarray,letters:array[1..sequencelengthplus1] of string;
tasks: array of ITask;
LTask: ITask;
Event1:TEvent;
sequencesection:TCriticalSection;
function findsequencex(index: Integer): TProc;
begin
Result := procedure
var
counter,m:integer;
r:integer;
z:string;
lettersx:array[1..sequencelengthplus1] of string;
begin
for m:=1 to sequencelengthplus1-1 do
lettersx[m]:=letters[m];
randomize;
counter:=1;
repeat
r:=random(26)+1;
z:=alphabetarray[r]; //randomly find letters until matched with the sequence
if z=letters[counter] then
begin
copyarray[counter]:=z;
counter:=counter+1; // increase counter when successfully found a match
end
else
counter:=1; // if match fails start again and look for the first letter
if (counter=sequencelengthplus1) then
begin // if all letters found in correct order find one more letter as a check
sequencesection.Acquire; //critical section start
r:=random(26)+1;
z:=alphabetarray[r];
TInterlocked.CompareExchange(sequencenum,r,0);
copyarray[sequencelengthplus1]:=z;
Event1.SetEvent; // set in motion the process to stop all other tasks
sequencesection.release; // critical section end
end;
until (Event1.WaitFor(0)=wrSignaled); // check to see if all letters of the sequence has been found
end;
end;
procedure Parallel2;
var
i,sequencevalue,j: Integer;
begin
Event1:=TEvent.Create(nil,true,false,'noname'); // sequence checker
Event1.resetevent;
sequencenum := 0;
Ticks := TThread.GetTickCount;
SetLength(Tasks, WorkerCount); // number of parallel tasks to undertake
for i := 0 to WorkerCount-1 do
Tasks[i]:=TTask.Run(findsequencex(i));
TTask.WaitForAny(Tasks); // wait for the first one to successfully finish
TThread.Synchronize(nil,
procedure
begin
for LTask in Tasks do
LTask.Cancel; // kill the remaining tasks
TInterlocked.Add (sequencevalue, sequencenum); // note the random letter check
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel time ' + Ticks.ToString + ' ms, last random alphabet sequence number: ' + sequencenum.ToString+' random letter is = '+alphabetarray[sequencevalue]);
end;
begin
sequencesection:=TCriticalSection.Create;
for m:=1 to (sequencelengthplus1-1) do
begin
letters[m]:=copy(sequencetofind,m,1);
writeln(letters[m]);
end;
i:=0;
for v:='a' to 'z' do
begin
i:=i+1;
alphabetarray[i]:=v;
end;
try
begin
Parallel2; // call the parrallel procedure
writeln('finished');
for m:=1 to sequencelengthplus1 do
writeln(copyarray[m]);
if (Event1.WaitFor(0)=wrSignaled) then
begin
writeln('event signaled');
if (sequencenum=0) then writeln('sequence is null');
end;
Event1.Free;
sequencesection.free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
You've got masses of shared global variables that you access without any synchronization. For instance:
if z = letters[counter] then
begin
copyarray[counter] := z;
counter := counter + 1;
// increase counter when successfully found a match
end
Here copyarray is a global that is shared between all the tasks. This data race alone could result in the error you see. There are other similar races. And I am sure that there are many many more problems. The entire code cannot be salvaged. You need to throw it away and start again.
Here are some tips:
Pick a simpler task to begin learning about parallel programming.
Find a good book or tutorial on the subject. Start small and work up to your actual problem.
Stop using global variables. Sharing data with globals can only lead to pain. Sharing is your enemy. Keep it to a minimum.
If you need to share data do so in an explicit manner rather than using globals. And make sure the access to shared data is synchronized.
Don't call Randomize from inside your thread. It's not threadsafe. Call it once at startup.
Random is not threadsafe. Find a thread safe PRNG, or synchronise calls to Random. The former option is to be preferred.
Don't call TThread.Synchronize from the main thread.
Manage lifetime the standard way. When you create an object, use try and finally to protect its lifetime. Don't create objects in one function and destroy them in some other function.
Format your code so that it is readable. If you cannot read your code, how will you ever understand it?
With the greatest of respect, it's clear that you've not mastered serial programming yet. You should aim to be proficient at serial programming before moving to parallel programming. Parallel programming is at least an order of magnitude harder.
With that in mind, try to write a good, clean version of your program in serial form. Then think about how to transform it to a parallel version.
I want to be able to make multiple threads do work on a single TStringList. This is my current Thread code for using a single thread to do the work;
type
TFilterThread = class(TThread)
protected
procedure Execute; override;
public
lCombos : TStringList;
//Public Vars
end;
procedure TFilterThread.Execute;
var
I: integer;
HTML: String;
frm1 : TForm1;
splitLogin: TStringList;
validCount: Integer;
begin
validCount := 0;
for I := 0 to lCombos.Count - 1 do
begin
if Terminated then Exit();
Unit1.Form1.ListBox1.ItemIndex := i;
try
HTML := Unit1.Form1.IdHTTP1.Get(EmailCheckURL + lCombos[i]);
if AnsiPos('You indicated you', HTML) > 0 then
begin
//Do stuff
end;
except
Continue;
end;
end;
Showmessage('Finished!');
Unit1.Form1.Button1.Caption := 'Start';
end;
To Start the thread I use;
lComboLsit := TStringList.Create;
for I := 0 to listBox1.Items.Count -1 do
lComboLsit.Add(listBox1.Items[i]);`
iTFilterThread := TFilterThread.Create(True);
iTFilterThread.FreeOnTerminate := True;
iTFilterThread.lCombos := lComboLsit;
iTFilterThread.Start;
How would I introduce another thread to also do work on the lCombos list so that the operation would complete quicker?
The answer is that it depends. If the string list is not modified, then there's nothing to do. If the string list is modified then what is best depends critically on your specific usage.
Looking at your code, your program may not be CPU bound and so adding more threads may not help. Your program's bottleneck will the HTTP communication. However, despite not being CPU bound it is plausible that running multiple threads will reduce the impact HTTP latency. You can benchmark to find out. You don't appear to be modifying the string list so there's no race problems to be concerned with there.
However, here's a problem:
Unit1.Form1.IdHTTP1.Get(EmailCheckURL + lCombos[i]);
That will work, and is thread safe so long as TIdHTTP is thread safe. But it's pretty ugly to allow a thread to access a component on a form like that. And I don't see any real sense or need to share the TIdHTTP instance between threads. It would be far cleaner to let each thread instantiate and use their own TIdHTTP component.
Of course, you will need to decide on a policy for dividing the work between all your threads. You could have a shared index that keeps track of the next item to process. Have each thread increment it atomically each time they take an item. A parallel for loop would be a good fit here. That's available in the latest version of Delphi, or in any decent third party parallel library.
You do have some problems with your code. In the thread procedure you do this:
Unit1.Form1.ListBox1.ItemIndex := i;
....
Unit1.Form1.Button1.Caption := 'Start';
You cannot access VCL components from a thread.
And then you do this
ShowMessage('Finished!');
Don't show UI from a thread.
A point of idiom. Instead of looping over the items in your list box you can simply do this:
lComboLsit.Assign(listBox1.Items);
I'm trying to make a multithreaded application (up to lets say 100 thread) in Delphi XE5,
all the thread are going to use/change a listbox in the main form,
here is my code:
Main Unit:
private
//list to hold the dynamic created threads
SendThreads : TObjectList;
public
mutex : boolean;
...
procedure TMainForm.FormCreate(Sender: TObject);
begin
...
mutex := false;
SendThreads := TObjectList.Create;
end;
...
//Button to create and start the threads
procedure TMainForm.btnWorkClick(Sender: TObject);
var
Thread : TSendThread;
i, nbr : integer;
begin
if SendThreads.Count < spnThreadCount.Value then
begin
for I := 1 to spnThreadCount.Value - SendThreads.Count do
begin
nbr := SendThreads.Add(TSendThread.Create(true));
Thread := TSendThread(SendThreads.Items[nbr]);
Thread.FreeOnTerminate := true;
Thread.Start;
end;
end;
end;
Thread Unit:
uses MainUnit;
procedure TSendThread.Execute;
begin
QueryList;
end;
//Basically, this procedure checks if the item in the listbox contains '- Done.' which means that this
//item has been done by another thread, if not, the current thread deal with this item.
procedure TSendThread.QueryList;
var i : integer;
S : String;
begin
i := 0;
while i < MainForm.lstURL.Count do
begin
while MainForm.mutex do;
MainForm.mutex := true;
if pos(' - Done.', MainForm.lstURL.Items[i]) = 0 then
begin
S := MainForm.lstURL.Items[i];
Delete(S, 1, pos('?txt=', S) + length('?txt=') - 1);
MainForm.Memo1.Lines.Add(MainForm.lstURL.Items[i]);
MainForm.lstURL.Items[i] := MainForm.lstURL.Items[i] + ' - Done.';
MainForm.mutex := false;
SendAd(URL, S);
end
else
begin
Inc(i);
MainForm.mutex := false;
end;
end;
end;
This method works if the number of the threads are less than 4, but if it's more i get redundant result (2 or more threads do the same item).
Now i'm fairly new to threads and multithreading, and i was wondering if this is the right way to do it.
In addition to what Ken said about UI safety, you are getting threads processing the same item because you have a race condition on your mutex variable. Multiple threads may see mutex=false at the same time as they are not actually syncing with each otber. You need to use a true mutex. Look at the TMutex class. Or just wrap the majority of your code in TThread.Synchronize() and let the VCL handle the syncing for you. But then that defeats the purpose of using threads.
You are using the completely wrong design for your requirement. You need to separate your worker thread logic from your UI logic. There are a few different ways you can do that.
Put your work jobs into a thread-safe queue, like TThreadList<T> or TThreadedQueue<T>. Each thread can check the queue periodically and pull the next available job if there is one.
A. A variation is to use an I/O Completion Port as the queue. Post jobs to the IOCP using PostQueuedCompletionStatus(), and have each thread use GetQueuedCompletionResult() to receive jobs. This allows the OS to do all the queuing and pulling for you, while allowing threads to sleep when there are no jobs available.
Put your threads into a pool in a sleeping state. When a new job is ready, check the pool. If a thread is available, pull it from the pool, pass the job to it, and wake it up. Otherwise put the job in a thread-safe queue. When a job is done, have that thread check the queue. If a job is available, pull it from the queue, otherwise put the thread in the poll and put it back to sleep.
In any case, when a job is done being processed, that thread can notify the main thread of the job result, using TThread.Synchronize(), TThread.Queue(), or any other inter-thread communication of your choosing. The main thread can then update the UI as needed.
At no point should the threads touch the UI to discover new jobs.
Delphi used: 2007
Hello everyone,
I have a TListView with ViewStyle set to vsReport. When I click on a button, I launch about 50 threads. Each thread has a TListItem component. Each TListItem has a SubItem that is a timer. It starts at 250 and goes all the way down to 0. The user is able to see each timer decreasing in the TListView.
I have written the following code:
procedure TThreadWorker.DeleteTickets;
begin
ListItem.Delete;
end;
procedure TThreadWorker.UpdateTimer;
begin
ListItem.SubItems[1] := IntToStr(Timer);
end;
procedure TThreadWorker.TimerCounter;
begin
Timer := 300;
repeat
Sleep(1000);
Dec(Timer);
Synchronize(UpdateTimer);
until (Timer = 0);
Synchronize(DeleteTickets);
end;
And... it works! But here's the thing: all these synchronizations seem to unnecessarily overload the CPU. Obviously, it's a bigger problem when I launch more threads (100, 200 or 300) or when I use a weaker computer. At first, I wasn't sure it was the synchronizations; but if I deactivate them, the CPU is no more overloaded.
To be frank, it's not that much of an issue. However, I have the feeling that decrementing timers shouldn't cause any sort of CPU overload: my code is probably not right. I tried calling UpdateTimer less often, and while it softens the CPU overload, it doesn't fix it in the end. Furthermore, I'd like the user to see the timer updated each second. The timer also needs to be as precise as possible.
Thank you.
I think you have placed the cart ahead of the horse here. Having all your threads synchronize into the main thread, each with their own timer and message queue, will place a heavy burden on the system. What's more, often times you don't want to burden your threads with running a message loop.
A better approach in my view is to place a single timer in the main thread. When it ticks, have it retrieve progress from each thread or task that it needs to report on. You'll need to serialize access to that progress, but that's not expensive.
I think threads are more expensive to the CPU than you might think. From what I remember, the CPU has overhead to swap each thread in and out of the cache. With the CPU swapping out 50 different threads, I'm not surprised its overloading
One solution might be to extend the TTimer component then dynamically create 50 of those rather than 50 threads. TTimer uses the windows api instead of threads. (The code below is untested, but should at least give you the idead)
TMyTimer = class(TTimer)
begin
public
Timer: integer;
ListItem: TListItem;
end;
...
procedure ButtonClick(Sender: TObject)
begin
for i := 0 to 50 do
begin
ltimer = TMyTimer.Create;
ltimer.Timer := 300;
ltimer.ListItem := TListItem.Create;
//initialize list item here
ltimer.OnTimer := DecTimer;
end;
end;
procedure DecTimer(Sender: TObject)
begin
dec(TMytimer(Sender).Timer);
TMyTimer(Sender).ListItem.SubItem[1] := StrToInt(TMytimer(Sender).Timer)
end;
If the threads are all starting at the same time, try doing something like having one thread control up to 25 timer. i.e. For 50 timers you only have two threads. The timer event would then just loop through its 25 counters and decrement them. You'd still need to use synchronize for this.
The answer to this question might be of interest:
How expensive are threads?
Here is example using TThread.Queue and a TSimpleEvent for timing the counter instead of a Sleep().
Type
TThreadWorker = Class(TThread)
private
FTimer : Integer;
FListItem : TListItem;
procedure Execute; override;
procedure UpdateTimer;
procedure DeleteTicket;
public
constructor Create( aListItem : TListItem);
End;
constructor TThreadWorker.Create(aListItem : TListItem);
begin
Inherited Create(false);
FListItem := aListItem;
Self.FreeOnTerminate := true;
end;
procedure TThreadWorker.Execute;
var
anEvent : TSimpleEvent;
begin
anEvent := TSimpleEvent.Create(nil,true,false,'');
try
FTimer := 300;
repeat
anEvent.WaitFor(1000);
Queue(UpdateTimer);
Dec(FTimer);
until (FTimer = 0);
Self.Synchronize( DeleteTicket); // <-- Do not Queue this !
finally
anEvent.Free;
end;
end;
procedure TThreadWorker.UpdateTimer;
begin
FListItem.SubItems[1] := IntToStr(FTimer);
end;
procedure TThreadWorker.DeleteTicket;
begin
FListItem.Delete;
end;
Just a note, the DeleteTicket must be synchronized. The thread is terminated when execute is done, and anything on the queue will be left dangling.