Delphi makes threading a very easy process, I’ve used it on numerous projects however there’s always room to learn new tricks.
When multiple threads want to access the same shared data you have to lock it to avoid complications, in my projects I use a TCriticalSection.
The Delphi docs and even my Mastering Delphi book were very vague on the use of this and so I’m hoping that this post will help others avoid the pitfall of misuse I did:
I assumed that I could use a Critical Section like so:
//vars, I use them as part of a class
var
cs : TCriticalSection;
anInt: integer;
aList: TList;
//create the CriticalSection when my class is created
TMyClass.Create
begin
cs := TCriticalSection.Create;
end;
//thread wants to access the int
cs.Enter;
doSomethingtoInt(anInt);
cs.leave;
//thread want sto do something to the TList
cs.Enter;
aList.add(anItem);
cs.Leave;
This is very wrong for the following reasons (and probably more):
1) I should have use a TThreadSafeList with auto locking built in for thread safe use
2) Using the same CriticalSection on two different sections of data is completely wrong. Each data that needs locking and is used in different routines or accessed at different times should have it’s own CriticalSection like so:
//Global vars
var
iCS : TCriticalSection;
lCS : TCriticalSection
anInt: integer;
aList: TList;
//create the CriticalSection when my class was created
TMyClass.Create
begin
iCS := TCriticalSection.Create;
lCS := TCriticalSection.Create;
end;
//thread wants to access the int
iCS.Enter;
doSomethingtoInt(anInt);
iCS.leave;
//thread wants to do something to the TList
aCS.Enter;
aList.add(anItem);
aCS.Leave;
Again, I must stress that if you need to use a TList with threads a much saner and safer option is to use a TThreadSafeList which locks the contained TList for you. If you need to access the internal TList of a TThreadSafeList the following code works great:
with threadedList.LockList do
try
for x := 0 to Count – 1 do begin
doSomething(items[x]);
end;
finally
threadedList.UnlockList;
end;
However simple actions such as adding an item are like so:
threadedList.Add(anItem);
A simple unit containing a class that spawns several threads and uses TCriticalSections to protect the data would look like so:
unit myClassUnit;
interface
uses
SyncObjs;type
myClass = class
private
anInt: Integer;
aWord: word;
intCS: TCriticalSection;
wordCS: TCriticalSection;
public
Constructor Create;
Destructor Destroy;
Procedure IncInt;
Procedure IncWord;
procedure decInt;
Procedure spawnThread;
end;implementation
{ myClass }
constructor myClass.Create;
begin
anInt := 0;
aWord := 0;
intCS := TCriticalSection.Create;
wordCS := TCriticalSection.Create;
end;destructor myClass.Destroy;
begin
intCS.Free;
wordCS.Free;
end;procedure myClass.decInt;
begin
intCS.Enter;
dec(anInt);
intCS.Leave;
end;procedure myClass.IncInt;
begin
intCs.Enter;
inc(anInt);
intCS.Leave;
end;procedure myClass.IncWord;
begin
wordCS.Enter;
inc(aWord);
wordCS.Leave;
end;procedure myClass.spawnThread;
var
aThread : TMyThread;
begin
aThread := TMyThread.Create(False);
end;end.
Usage would be as simple as
var
theClass : TMyClass;
x : integer;
begin
theClass := TMyClass.Create;
for x := 0 to 3 do begin
theClass.spawnThread;
end;
with the threads calling the inc or dec procedures like so:
theClass.incInt;
theClass.decInt;
theClass.incWord;
with no risk of data corruption being caused by multiple or conflicting calls to the procedures by different threads.