Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Locking and unlocking of a global object in a multi-threaded application

997 views
Skip to first unread message

Enquiring Mind

unread,
Jan 25, 2007, 11:01:03 AM1/25/07
to
Hi,

I need to lock/unlock a (custom) global object in a multi-threaded
application to ensure that the object (and its methods) may be accessed by
only one thread at a time, preferably using Delphi 7 classes rather than
Win323 API calls. Does the Delphi class TCriticalSection have the same
effect as an O.S. Mutex, except that its scope is limited to the process it
is created in, whilst the Mutex object is created in the O.S. space, and is
therefore visible to multiple processes?

The on-line help indicates that TCriticalSection object may be used to limit
concurrent access to a section of code enclosed in a CriticalSection.Acquire
.. CriticalSection.Release construct to a single thread ("TCriticalSection
allows a thread in a multi-threaded application to temporarily block other
threads from accessing a block of code"). This raises the question: If there
are multiple sections of code that are enclosed in the same construct using
the same TCriticalSection object, will the net effect be the same as if a
Mutex object were being used?

Consider, for example, the following code:

unit UnitTMyObject;

interface

type
TMyObject= class
FData: string;
procedure SetValue(Value string);
function GetValue: string;
end;

var
MyObject: TObject;
CriticalSection: TCriticalSection;

implementation

uses SyncObjs;

procedure TMyObject.SetValue(Value: String);
begin
CriticalSection.Acquire;
try
FData:= Value;
finally
CriticalSection.Release;
end;
end;

function TMyObject.GetValue:string;
begin
CriticalSection.Acquire;
try
Result:= FData;
finally
CriticalSection.Release;
end;
end;

initialization
MyObject:= TMyObject.Create;
CriticalSection:= TCriticalSection.Create;

finalization
MyObject.Free;
CriticalSection.Free;
end.

Clearly if thread B tries to execute the method SetValue while the method is
still being executed by thread A, thread B will be locked out, and have to
wait until thread A has finished the execution of SetValue. This fits in
with the definition that a CriticalSection is a locking device for a
specific section of code. But what happens if thread B tries to execute a
different method to that being executed by thread A? Suppose that while
thread A is executing the SetValue method, thread B tries to execute the
GetValue method. Will thread B be locked out until thread A has finished
executing SetValue?

Does the TCriticalSection.Acquire method translate into a waiting loop along
the following lines?

procedure TCriticalSection.Acquire;
begin
while Self.FLocked do
{Nothing};
end;

If the mechansim is based solely on something that is equivalent to the
FLocked flag in the above code, presumably there is no guarantee that
MyObject method executions for waiting threads will be executed in the order
that they were called, becase when control of a critical section is released
by one thread, the next thread to acquire control will be the waiting thread
that happens to fall in the next time slice. Is that the case, or is there
some underlying queuing mechanism, indicating thread, and critical section?

Thanks for any clarification.

Enquiring Mind


Xavier

unread,
Jan 25, 2007, 12:51:22 PM1/25/07
to
Enquiring Mind wrote:
> Hi,

>
> Does the Delphi class TCriticalSection have the same
> effect as an O.S. Mutex, except that its scope is limited to the process it
> is created in, whilst the Mutex object is created in the O.S. space, and is
> therefore visible to multiple processes?

To quote the MSDN library:

"A critical section object provides synchronization similar to
that provided by a mutex object, except that a critical section can be
used only by the threads of a single process."

So, yes.

> The on-line help indicates that TCriticalSection object may be used to limit
> concurrent access to a section of code enclosed in a CriticalSection.Acquire
> .. CriticalSection.Release construct to a single thread ("TCriticalSection
> allows a thread in a multi-threaded application to temporarily block other
> threads from accessing a block of code"). This raises the question: If there
> are multiple sections of code that are enclosed in the same construct using
> the same TCriticalSection object, will the net effect be the same as if a
> Mutex object were being used?

Yes. "*A* section/block of code" is inaccurate. A CS is an object a
thread can own, blocks of code are irrelevant.

> Consider, for example, the following code:
>

> type
> TMyObject= class
> FData: string;
> procedure SetValue(Value string);
> function GetValue: string;
> end;
>
> var
> MyObject: TObject;
> CriticalSection: TCriticalSection;

If the CS is only used inside the class, it should be declared (and
managed) as a member of the class.

> Clearly if thread B tries to execute the method SetValue while the method is
> still being executed by thread A, thread B will be locked out, and have to
> wait until thread A has finished the execution of SetValue. This fits in
> with the definition that a CriticalSection is a locking device for a
> specific section of code. But what happens if thread B tries to execute a
> different method to that being executed by thread A?

It will still be locked waiting for A. Everything between CS.Acquire and
CS.Release, no matter where it is, is only accessible to one thread at a
time.

> Suppose that while
> thread A is executing the SetValue method, thread B tries to execute the
> GetValue method. Will thread B be locked out until thread A has finished
> executing SetValue?

If B calls GetValue while A is running SetValue, it will wait until A
releases the CS, then acquires the CS and runs the part in GetValue that
is protected by the CS.

I like to think of Acquire/Enter() as WaitForOwnership().

> Does the TCriticalSection.Acquire method translate into a waiting loop along
> the following lines?
>
> procedure TCriticalSection.Acquire;
> begin
> while Self.FLocked do
> {Nothing};
> end;

More or less;

> If the mechansim is based solely on something that is equivalent to the
> FLocked flag in the above code, presumably there is no guarantee that
> MyObject method executions for waiting threads will be executed in the order
> that they were called, becase when control of a critical section is released
> by one thread, the next thread to acquire control will be the waiting thread
> that happens to fall in the next time slice. Is that the case, or is there
> some underlying queuing mechanism, indicating thread, and critical section?

MSDN states there is a FIFO queue, but it also states this behavior is
abandoned since Windows Server 2003 SP1:

"Starting with Windows Server 2003 SP1, threads waiting on a
critical section do not acquire the critical section on a first-come,
first-serve basis. This change increases performance significantly for
most code. However, some applications depend on FIFO ordering and may
perform poorly or not at all on current versions of Windows (for
example, applications that have been using critical sections as a
rate-limiter)."

Remy Lebeau (TeamB)

unread,
Jan 25, 2007, 2:24:51 PM1/25/07
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in
message news:45b8d410$1...@newsgroups.borland.com...

> I need to lock/unlock a (custom) global object in a multi-threaded
> application to ensure that the object (and its methods) may be
> accessed by only one thread at a time

That is what synchronization objects (critical sections, mutexes,
semaphores, etc) are for.

> preferably using Delphi 7 classes rather than Win323 API calls.

Why not use the API? Using the VCL classes will have runtime overhead
to them, and they don't offer anything extra that the API doesn't
already provide directly.

> Does the Delphi class TCriticalSection have the same effect as
> an O.S. Mutex

TCriticalSection has the same effect as an OS critical section,
because that is what it actually wraps. There is no native VCL
wrapper for a mutex. Though a critical section and a mutex act
similarly in general (although a mutex can use a timeout whereas a
critical section cannot).

> The on-line help indicates that TCriticalSection object may be used
> to limit concurrent access to a section of code enclosed in a
> CriticalSection.Acquire .. CriticalSection.Release construct

Yes. The same applies to a mutex as well (using a
WaitForSingleObject() .. ReleaseMutex() construct).

> If there are multiple sections of code that are enclosed in the
> same construct using the same TCriticalSection object, will the
> net effect be the same as if a Mutex object were being used?

Yes. A thread can "lock" a critical section or mutex multiple times.
This way, the thread does not deadlock itself. Just make sure that
each "lock" has a cooresponding "unlock".

> Consider, for example, the following code:

A slightly better design would be to put the TCriticalSection into the
TMyObject class as a member instead. Also, keep in mind that String
is reference-counted, so the cod you have shown is not completely
protecting your String data adequately. One thread can potenially
alter the contents of the String even though another thread has it
locked at the time. Get/SetValue() should make unique copies of the
data to prevent that.

Try the following:

unit UnitTMyObject;

interface

uses
SyncObjs;

type
TMyObject = class
private
FData: String;
FLock: TCriticalSection;
procedure SetValue(const Value: String);
function GetValue: String;
public
constructor Create;
destructor Destroy; override;
property Value: String read GetValue write SetValue;
end;

var
MyObject: TObject;

implementation

constructor TMyObject.Create;
begin
inherited;
FLock := TCriticalSection.Create;
end;

destructor TMyObject.Destroy;
begin
FLock.Free;
inherited;
end;

function TMyObject.GetValue: String;
begin
FLock.Acquire;
try
Result := Copy(FData, 1, Length(FData));
finally
FLock.Release;
end;
end;

procedure TMyObject.SetValue(const Value: String);
begin
FLock.Acquire;
try
FData := Copy(Value, 1, Length(Value));
finally
FLock.Release;
end;
end;

initialization
MyObject := TMyObject.Create;
finalization
MyObject.Free;
end.

> Clearly if thread B tries to execute the method SetValue while the
> method is still being executed by thread A, thread B will be locked
> out, and have to wait until thread A has finished the execution of
> SetValue.

Correct.

> But what happens if thread B tries to execute a different method to
> that being executed by thread A? Suppose that while thread A is
> executing the SetValue method, thread B tries to execute the
> GetValue method. Will thread B be locked out until thread A has
> finished executing SetValue?

Yes, because they are sharing the same lock. Only one thread an enter
the lock at a time.

> Does the TCriticalSection.Acquire method translate into a waiting
> loop along the following lines?

No. Acquire() calls EnterCriticalSection(), which is a very efficient
function:

procedure TCriticalSection.Acquire;
begin
EnterCriticalSection(FSection);
end;

There is no looping involved. The blocked thread enters an idle state
until the lock is released.

> If the mechansim is based solely on something that is equivalent
> to the FLocked flag in the above code

It is not.

> presumably there is no guarantee that MyObject method executions
> for waiting threads will be executed in the order that they were
called

They is no guarantee about the order.

> becase when control of a critical section is released by one thread,
> the next thread to acquire control will be the waiting thread that
> happens to fall in the next time slice.

Correct. But the OS will ensure that all wating threads will
evntually get a shot at the lock. You won't get into a situation
where a given thrad is starved for access indefinately. Though it may
take a little longer than expected. Which is why locked sections of
code should be as small and as fast as possible in general, whether
you are using a critical section or a mutex. Block access to just the
bare minimum that is actualy shared across thread boundaries, and let
everything else run in the local thread context without blocking other
threads.


Gambit


Peter Below (TeamB)

unread,
Jan 25, 2007, 2:36:09 PM1/25/07
to
Enquiring Mind wrote:

> I need to lock/unlock a (custom) global object in a multi-threaded
> application to ensure that the object (and its methods) may be
> accessed by only one thread at a time, preferably using Delphi 7
> classes rather than Win323 API calls. Does the Delphi class
> TCriticalSection have the same effect as an O.S. Mutex, except that
> its scope is limited to the process it is created in, whilst the
> Mutex object is created in the O.S. space, and is therefore visible
> to multiple processes?

TCriticalSection is a shallow wrapper around OS critical sections (see
InitializeCriticalSection et al. in the SDK docs). Critical sections
are the preferred method of guarding a shared resource inside a
process, since they have far less overhead than a kernel object like a
mutex or semaphor.

>
> The on-line help indicates that TCriticalSection object may be used
> to limit concurrent access to a section of code enclosed in a
> CriticalSection.Acquire .. CriticalSection.Release construct to a
> single thread ("TCriticalSection allows a thread in a multi-threaded
> application to temporarily block other threads from accessing a block
> of code"). This raises the question: If there are multiple sections
> of code that are enclosed in the same construct using the same
> TCriticalSection object, will the net effect be the same as if a
> Mutex object were being used?

Yes. If you want to guard an object I would add two public methods Lock
and Unlock to it that enter and leave a TCriticalSection private to the
object. In fact it is even better (design-wise) to just make *all*
public methods (including any property getters and setters) to enter
the critcial section at the start and leaving it at the end (using
try..finally blocks). That makes the object thread-safe without any
special action required by code using the object. Much more reliable,
but sometimes at the price of reduced performance, if the public
methods execute so quickly in general that the overhead of the critical
section becomes significant.


--
Peter Below (TeamB)
Don't be a vampire (http://slash7.com/pages/vampires),
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com
http://www.prolix.be

Enquiring Mind

unread,
Jan 26, 2007, 6:08:39 AM1/26/07
to

"Xavier" <n...@spam.com> wrote in message
news:45b8ede7$1...@newsgroups.borland.com...

>
> Yes. "*A* section/block of code" is inaccurate. A CS is an object a
> thread can own, blocks of code are irrelevant.
>
Thanks very much for that clarification. In view of it, maybe it would be a
good idea if the Delphi on-line help, and Delphi books such as the Mastering
Delphi series, were to be revised to give a more accurate definition of a
Critical Section and how it behaves!

>
> If the CS is only used inside the class, it should be declared (and
> managed) as a member of the class.
>

There is also a case for *not* making the CriticalSection a member of the
class to be protected, and not putting any CriticalSection.Acquire ..
CriticalSection.Release constructs into the code of the class methods, but
leave it to the client of the class to protect the method calls when
necessary. Suppose a client wishes to call a sequence of methods of the
class as an indivisible whole, transaction style. He could enclose these in
a single construct, thus locking out other threads until the whole sequence
of methods has been completed. If, on the other hand, the client relies on
each method being internally protected by a CriticalSection.Acquire ..
CriticalSection.Release construct, not only could there be a greater
overhead, but there is also the risk that another thread could acquire
control between one method and the next, so that the method sequence is no
longer executed as an indivisible whole. Finally, there may situations in
which the class is used in a single threaded program, in which case the use
of the Critical Section would be unnecessary.


>
> MSDN states there is a FIFO queue, but it also states this behavior is
> abandoned since Windows Server 2003 SP1:
>

Thanks for the useful information.

Regards,

Enquiring Mind


Enquiring Mind

unread,
Jan 26, 2007, 6:36:16 AM1/26/07
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:45b903e7$1...@newsgroups.borland.com...

>
>
> Why not use the API? Using the VCL classes will have runtime overhead
> to them, and they don't offer anything extra that the API doesn't
> already provide directly.
>
Based upon what I learned from Wirth's books, I have an in-built aversion to
using low level system features such as API functions in a high-level
application program, and will always seek to use high level, operating
system independent alternatives when they exist. This ensures maximum
intelligibility, portability, and longevity of the application program. I
would go so far as to say, if Delphi provides a wrapper to a Windows
Critical Section, why does it not also provide wrappers to Windows mutexes
and semaphores? I am not familiar with Kylix, but if these wrappers existed,
then one could port a Win32 Delphi program using these synchronization
facilities to a Linux machine with no change to the code.

>
> Yes. A thread can "lock" a critical section or mutex multiple times.
> This way, the thread does not deadlock itself. Just make sure that
> each "lock" has a cooresponding "unlock".
>

Thanks for that clarification.


> A slightly better design would be to put the TCriticalSection into the
> TMyObject class as a member instead. Also, keep in mind that String
> is reference-counted, so the cod you have shown is not completely
> protecting your String data adequately. One thread can potenially
> alter the contents of the String even though another thread has it
> locked at the time. Get/SetValue() should make unique copies of the
> data to prevent that.

See my comments on the possible disadvantages of this option in my post in
response to Xavier's post.


>
> No. Acquire() calls EnterCriticalSection(), which is a very efficient
> function:
>

> There is no looping involved. The blocked thread enters an idle state
> until the lock is released.
>

Presumably if there's no lock polling loop, when CriticalSection.Release is
called, the OS would need to broadcast a message to all waiting threads to
inform them that the lock has been released, and this may need to be handled
in the application's main message loop. Or does the idle waiting process
gets notified when the lock is unlocked by a simpler method?

Regards,

Enquiring Mind


Enquiring Mind

unread,
Jan 26, 2007, 6:42:40 AM1/26/07
to

"Peter Below (TeamB)" <none> wrote in message
news:xn0f1n17...@newsgroups.borland.com...

> Enquiring Mind wrote:
>
>
> Yes. If you want to guard an object I would add two public methods Lock
> and Unlock to it that enter and leave a TCriticalSection private to the
> object. In fact it is even better (design-wise) to just make *all*
> public methods (including any property getters and setters) to enter
> the critcial section at the start and leaving it at the end (using
> try..finally blocks). That makes the object thread-safe without any
> special action required by code using the object. Much more reliable,
> but sometimes at the price of reduced performance, if the public
> methods execute so quickly in general that the overhead of the critical
> section becomes significant.
>
I like the idea of providing Lock an Unlock methods in a class to be
protected from concurrent access. This seems to overcome all the
disadvantages of making a CriticalSection object a private member of the
class to be protected that I mentioned in my post in reply to Xavier.

Regards,

Enquiring Mind

Xavier

unread,
Jan 26, 2007, 8:20:33 AM1/26/07
to
Enquiring Mind wrote:
> Suppose a client wishes to call a sequence of methods of the
> class as an indivisible whole, transaction style. He could enclose these in
> a single construct, thus locking out other threads until the whole sequence
> of methods has been completed.

That's when you introduce two public methods, something like Lock() and
Unlock(), whose jobs are simply to Acquire and Release the CS. Then:

Object.Lock();
try
... do anything with the object ...
... call multiple protected methods, whatever ...
finally
Object.Unlock();
end;

A CS can be acquired as many times as a thread feels like, as long as
each Acquire() is accompanied by a Release().

> If, on the other hand, the client relies on
> each method being internally protected by a CriticalSection.Acquire ..
> CriticalSection.Release construct, not only could there be a greater
> overhead,

Acquiring a CS a second time or more is *far* less expensive than the
first time, so the overhead is insignificant. If you still want to avoid
the two calls to the OS, then sure, by all means, keep the CS out of the
class. But then you must be extra careful with it.

> but there is also the risk that another thread could acquire
> control between one method and the next, so that the method sequence is no
> longer executed as an indivisible whole.

With the method explained two quotes up, this wouldn't be a problem.

Xavier

unread,
Jan 26, 2007, 8:34:41 AM1/26/07
to
Enquiring Mind wrote:
> "Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
>> No. Acquire() calls EnterCriticalSection(), which is a very efficient
>> function:
>>
>> There is no looping involved. The blocked thread enters an idle state
>> until the lock is released.
>>
> Presumably if there's no lock polling loop, when CriticalSection.Release is
> called, the OS would need to broadcast a message to all waiting threads to
> inform them that the lock has been released, and this may need to be handled
> in the application's main message loop. Or does the idle waiting process
> gets notified when the lock is unlocked by a simpler method?

No message or notification. In fact, there is not even a notification if
the CS is destroyed with outstanding lock requests; to quote the MSDN:

"Unlike a mutex object, there is no way to tell whether a
critical section has been abandoned."

"If a thread terminates while it has ownership of a critical
section, the state of the critical section is undefined."

"If a critical section is deleted while it is still owned, the
state of the threads waiting for ownership of the deleted critical
section is undefined."


When a CS is released by a thread, the next waiting thread is awakened;
<MSDN> If the critical section object is currently owned by another
thread, EnterCriticalSection waits indefinitely for ownership. </MSDN>

Remy Lebeau (TeamB)

unread,
Jan 26, 2007, 3:57:32 PM1/26/07
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in
message news:45b9...@newsgroups.borland.com...

> if Delphi provides a wrapper to a Windows Critical Section, why
> does it not also provide wrappers to Windows mutexes and
> semaphores?

You would have to ask Borland that. You can write your own wrappers
though. You can derive from the THandleObject class and override its
virtual Acquire() and Release() methods.

There is another OS synchronization object available that is wrapped
by the VCL, though - an event object, which is wrapped by the TEvent
and TSimpleEvent classes.

> Presumably if there's no lock polling loop, when
CriticalSection.Release
> is called, the OS would need to broadcast a message to all waiting
> threads to inform them that the lock has been released, and this may
> need to be handled in the application's main message loop.

That is not true at all. There is no message involved. If you really
want to know what a critical section does, you should pick up a book
that talks about the inner workings of the OS.


Gambit


Remy Lebeau (TeamB)

unread,
Jan 26, 2007, 4:42:29 PM1/26/07
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:45ba...@newsgroups.borland.com...

> MyObject.Lock;
> try
> MyObject.IntValue := MyObject.IntValue + 1;
> MyObject.StrValue := IntToStr(MyObject.IntValue);
> finally
> MyObject.Lock;
> end;

Whoops, smal typo, should be:

MyObject.Lock;
try
MyObject.IntValue := MyObject.IntValue + 1;
MyObject.StrValue := IntToStr(MyObject.IntValue);
finally
MyObject.Unlock; // <-- here
end;


Gambit


Remy Lebeau (TeamB)

unread,
Jan 26, 2007, 4:40:25 PM1/26/07
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in
message news:45b9...@newsgroups.borland.com...

> maybe it would be a good idea if the Delphi on-line help, and Delphi


> books such as the Mastering Delphi series, were to be revised to
give
> a more accurate definition of a Critical Section and how it behaves!

That is already covered by Microsoft's own documentation, since that
is where the CS coms from in the first place.

> There is also a case for *not* making the CriticalSection a member
of
> the class to be protected, and not putting any
CriticalSection.Acquire ..
> CriticalSection.Release constructs into the code of the class
methods,
> but leave it to the client of the class to protect the method calls
when
> necessary.

The case you describe does not negate the use of a private CS and
local locks. In fact, quite the opposite - it works better with them.

> Suppose a client wishes to call a sequence of methods of the class
as
> an indivisible whole, transaction style. He could enclose these in a
single
> construct, thus locking out other threads until the whole sequence
of
> methods has been completed.

A CS can be locked multiple times by a thread. The public methods can
still lock/unlock the CS locally, but you can expose additional
Lock/Unlock() methods to allow transactions as well (which each method
acts as a local transaction). The local lock/unlock operations will
not invoke extra overhead when called from the same thread that begun
another transaction using the same CS. What you are describing is a
very common design that many people already use.

Besides, you need a private CS and local locks when using a
transactional model anyway. Think about it - thread A starts a
transaction involving multiple methods and properties. At the same
time, thread B wants to call only one of the methods or access only
one of the properties. That method/property still needs a local lock
in order to block thread B until thread A is finished with its
transaction. So you are still in a situation where you need a
class-wide CS as a member.

For example:

unit UnitTMyObject;

interface

uses
SyncObjs;

type
TMyObject = class
private

FInt: Integer;
FStr: String;
FLock: TCriticalSection;
function GetIntValue: Integer;
function GetStrValue: String;
procedure SetIntValue(const Value: Integer);
procedure SetStrValue(const Value: String);


public
constructor Create;
destructor Destroy; override;

procedure Lock;
procedure Unlock;
property IntValue: Integer read GetIntValue write
SetIntValue;
property StrValue: String read GetStrValue write
SetStrValue;
end;

var
MyObject: TMyObject;

implementation

constructor TMyObject.Create;
begin
inherited;
FLock := TCriticalSection.Create;
end;

destructor TMyObject.Destroy;
begin
FLock.Free;
inherited;
end;

procedure TMyObject.Lock;
begin
FLock.Acquire;
end;

procedure TMyObject.Unlock;
begin
FLock.Release;
end;

function TMyObject.GetIntValue: Integer;
begin
Lock;
try
Result := FInt;
finally
Unlock;
end;
end;

procedure TMyObject.SetIntValue(const Value: Integer);
begin
Lock;
try
FInt := Value;
finally
Unlock;
end;
end;

function TMyObject.GetStrValue: String;
begin
Lock;
try
Result := Copy(FStr, 1, Length(FStr));
finally
Unlock;
end;
end;

procedure TMyObject.SetStrValue(const Value: String);
begin
Lock;
try
FStr := Copy(Value, 1, Length(Value));
finally
Unlock;
end;
end;

initialization
MyObject := TMyObject.Create;
finalization
MyObject.Free;
end.

unit UnitTThread1;

interface

uses
Classes;

type
TThread1 = class(TThread)
protected
procedure Execute; override;
end;

implementation

uses
UnitTMyObject;

procedure TThread1.Execute;
begin
while not Terminated do
begin


MyObject.Lock;
try
MyObject.IntValue := MyObject.IntValue + 1;
MyObject.StrValue := IntToStr(MyObject.IntValue);
finally
MyObject.Lock;
end;

Sleep(1000);
end;
end;

unit UnitTThread2;

interface

uses
Classes;

type
TThread2 = class(TThread)
protected
procedure Execute; override;
end;

implementation

uses
UnitTMyObject;

procedure TThread2.Execute;
var
S: String;
begin
while not Terminated do
begin
S := MyObject.StrValue;
Sleep(5000);
end;
end;


> If, on the other hand, the client relies on each method being
internally
> protected by a CriticalSection.Acquire .. CriticalSection.Release
> construct, not only could there be a greater overhead

Thre is no extra overhead in locking a CS that is already locked by
the same thread.

> there is also the risk that another thread could acquire control
between
> one method and the next

That is exactly why you need to lock each individual method locally
even when a transaction is running. If you don't, one thread can
enter an unprotected method while another is running a transaction,
and thus they can mess each other up.

> Finally, there may situations in which the class is used in a single
threaded
> program, in which case the use of the Critical Section would be
unnecessary.

One way to approach that is to simply put all of your data into a base
class with empty Lock/Unlock() methods, and then derive another class
that overrides them to lock/unlock a CS. Use the base class as-is in
a single-thread situation, and the CS derivitive in a multi-threaded
situation.

An alternative approach is to use policy-based programming instead.
To allow the class to be used generically, you can wrap the CS in its
own wrapper with a generic interface. That class type is then given
to the main class in a multi-threaded situation and not given in a
single-threaded situation. This also allows you to specify the type
of lock to use (CS, mutex, semaphore, event) without the main class
having to care about it. For example:

unit UnitTMyObject;

interface

uses
SyncObjs;

type
TLock = class
public
constructor Create; virtual;
procedure Lock; virtual; abstract;
procedure Unlock; virtual; abstract;
end;
TLockClass = class of TLock;

TLockCritSec = class(TLock)
private
CS: TCriticalSection;
public
constructor Create; override;
destructor Destroy; override;
procedure Lock; override;
procedure Unlock; override;
end;

TLockMutex = class(TLock)
private
Mutex: THandle;
public
constructor Create; override;
destructor Destroy; override;
procedure Lock; override;
procedure Unlock; override;
end;

TMyObject = class
private
FInt: Integer;
FStr: String;
FLock: TLock;
function GetIntValue: Integer;
function GetStrValue: String;
procedure SetIntValue(const Value: Integer);
procedure SetStrValue(const Value: String);
public
constructor Create(LockType: TLockClass = nil);
destructor Destroy; override;
procedure Lock;
procedure Unlock;
property IntValue: Integer read GetIntValue write
SetIntValue;
property StrValue: String read GetStrValue write
SetStrValue;
end;

var
MyObject1: TMyObject;
MyObject2: TMyObject;
MyObject:3 TMyObject;

implementation

constructor TLock.Create;
begin
inherited;
end;

constructor TLockCritSec.Create;
begin
inherited;
CS := TCriticalSection.Create;
end;

destructor TLockCritSec.Destroy;
begin
CS.Free;
inherited;
end;

procedure TLockCritSec.Lock;
begin
CS.Acquire;
end;

procedure TLockCritSec.Unlock;
begin
CS.Release;
end;

constructor TLockMutex.Create;
begin
inherited;
Mutex := CreateMutex(nil, FALSE, nil);
end;

destructor TLockMutex.Destroy;
begin
CloseHandle(Mutex);
inherited;
end;

procedure TLockCritSec.Lock;
begin
WaitForSingleObject(Mutex, INFINITE);
end;

procedure TLockCritSec.Unlock;
begin
ReleaseMutex(Mutex);
end;

constructor TMyObject.Create(LockType: TLockClass = nil);
begin
inherited;
if Assigned(LockType) then
FLock := LockType.Create;
end;

destructor TMyObject.Destroy;
begin
FLock.Free;
inherited;
end;

procedure TMyObject.Lock;
begin
if Assigned(FLock) then FLock.Lock;
end;

procedure TMyObject.Unlock;
begin
if Assigned(FLock) then FLock.Unlock;
end;

function TMyObject.GetIntValue: Integer;
begin
Lock;
try
Result := FInt;
finally
Unlock;
end;
end;

procedure TMyObject.SetIntValue(const Value: Integer);
begin
Lock;
try
FInt := Value;
finally
Unlock;
end;
end;

function TMyObject.GetStrValue: String;
begin
Lock;
try
Result := Copy(FStr, 1, Length(FStr));
finally
Unlock;
end;
end;

procedure TMyObject.SetStrValue(const Value: String);
begin
Lock;
try
FStr := Copy(Value, 1, Length(Value));
finally
Unlock;
end;
end;

initialization
MyObject1 := TMyObject.Create;
MyObject2 := TMyObject.Create(TLockCritSec);
MyObject3 := TMyObject.Create(TLockMutex);
finalization
MyObject1.Free;
MyObject2.Free;
MyObject3.Free;
end.


unit UnitTMainForm;

interface

type
TMainForm = class(TForm)
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
end;

implementation

uses
UnitTMyObject;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
MyObject1.IntValue := GetTickCount;
MyObject1.StrValue := MyObject1.IntValue;
end;


unit UnitTThread1;

interface

uses
Classes;

type
TThread1 = class(TThread)
protected
procedure Execute; override;
end;

implementation

uses
UnitTMyObject;

procedure TThread1.Execute;
var
S: String;
begin
while not Terminated do
begin
MyObject2.Lock;
try
MyObject2.IntValue := MyObject2.IntValue + 1;
MyObject2.StrValue := IntToStr(MyObject2.IntValue);
finally
MyObject2.Lock;
end;
S := MyObject3.StrValue;
Sleep(1000);
end;
end;

unit UnitTThread2;

interface

uses
Classes;

type
TThread2 = class(TThread)
protected
procedure Execute; override;
end;

implementation

uses
UnitTMyObject;

procedure TThread2.Execute;
var
S: String;
begin
while not Terminated do
begin
MyObject3.Lock;
try
MyObject3.IntValue := MyObject3.IntValue + 1;
MyObject3.StrValue := IntToStr(MyObject3.IntValue);
finally
MyObject3.Lock;
end;
S := MyObject2.StrValue;
Sleep(1000);
end;
end;


Gambit


Enquiring Mind

unread,
Jan 27, 2007, 5:46:21 AM1/27/07
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:45ba...@newsgroups.borland.com...
>
> "Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in
> message news:45b9...@newsgroups.borland.com...
>
>> maybe it would be a good idea if the Delphi on-line help, and Delphi
>> books such as the Mastering Delphi series, were to be revised to
> give
>> a more accurate definition of a Critical Section and how it behaves!
>
> That is already covered by Microsoft's own documentation, since that
> is where the CS coms from in the first place.
>

On the basis that TCriticalSection is a Delphi class that is part of the
Delphi library, IMHO it should be clearly and accurately defined in the
Delphi documentation, without delegation to the MS documentation of the API
class that's used in its implementation. In this way the class stands a
chance of being platform- independent. I understand that locking and
unlocking of objects is now built into C# language or Foundation Library as
a high level element, meaning that in principle, at least, it should work on
any platform.


> The case you describe does not negate the use of a private CS and
> local locks. In fact, quite the opposite - it works better with them.
>

I fully agree about the utility of making the CS a private variable of the
object to be protected, but I am less convinced about the utility of putting
a Lock .. Unlock construct inside every method implementation.


>
> A CS can be locked multiple times by a thread. The public methods can
> still lock/unlock the CS locally, but you can expose additional
> Lock/Unlock() methods to allow transactions as well (which each method
> acts as a local transaction). The local lock/unlock operations will
> not invoke extra overhead when called from the same thread that begun
> another transaction using the same CS. What you are describing is a
> very common design that many people already use.
>

If I understand you correctly, suppose that a class TMyObject exposes, in
addition to methods Lock and Unlock, methods Method1, Method2, and that the
latter methods do not incorporate an inner Lock..Unlock construct. Consider
the code:

with MyObject do
begin
Lock;
Method1;
Method2;
Unlock;
end;

In this case MyObject is clearly locked until both methods have been
completed (because no other thread could gain control and unlock the object,
assuming that every thread wraps any calls to object methods in a
Lock..Unlock construct). Consider next alternative code that wraps local
Lock..Unlock constructs around each method call. The logic is equivalent to
putting Lock..Unlock constructs inside the method implementations.

with MyObject do
begin
Lock; {Global object lock}
Lock; {Local method lock}
Method1;
Unlock;{Local method unlock}
Lock; {Local method lock}
Method2;
Unlock; ;{Local method unlock}
Unlock; {Global object unlock}
end;

Here the second Lock statement should have no effect, because MyObject is
already locked. However the first local Unlock statement will unlock the
object temporarily, opening up the opportunity for another waiting thread to
acquire control. Thus the behaviour of this version is potentially different
to that of the first version.

In this method MyObject will be unlocked each time a method is exited, and a
lock reacquired each time that a method is entered. However there is the
remote possibility that when it is unlocked (e.g. at the end of the
MyObject.GetIntValue method), another thread could grab control before the
first thread relocks the object. Or is that impossible?


>
>> there is also the risk that another thread could acquire control
> between
>> one method and the next
>
> That is exactly why you need to lock each individual method locally
> even when a transaction is running. If you don't, one thread can
> enter an unprotected method while another is running a transaction,
> and thus they can mess each other up.

This shouldn't happen if the thread that wishes to use a single method
encloses the call in a MyObject.Lock..MyObject.Unlock construct.
>

> One way to approach that is to simply put all of your data into a base
> class with empty Lock/Unlock() methods, and then derive another class
> that overrides them to lock/unlock a CS. Use the base class as-is in
> a single-thread situation, and the CS derivitive in a multi-threaded
> situation.
>
> An alternative approach is to use policy-based programming instead.
> To allow the class to be used generically, you can wrap the CS in its
> own wrapper with a generic interface. That class type is then given
> to the main class in a multi-threaded situation and not given in a
> single-threaded situation. This also allows you to specify the type
> of lock to use (CS, mutex, semaphore, event) without the main class
> having to care about it. For example:
>

Thanks for these interesting ideas!

Enquiring Mind


Remy Lebeau (TeamB)

unread,
Jan 27, 2007, 7:22:07 AM1/27/07
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in
message news:45bb...@newsgroups.borland.com...

> I fully agree about the utility of making the CS a private variable
of the
> object to be protected, but I am less convinced about the utility of
putting
> a Lock .. Unlock construct inside every method implementation.

Then you are not paying close enough attention to what is being
explained to you. The Lock/Unlock inside each method is MANDATORY for
what you have described. I've given you detailed examples to
demonstrate why.

> If I understand you correctly

You do not.

> suppose that a class TMyObject exposes, in addition to methods Lock
> and Unlock, methods Method1, Method2, and that the latter methods
do
> not incorporate an inner Lock..Unlock construct.

Those those methods are completely unprotected, and thus are not
thread-safe if they access any resources that have to be protected
from multi-threaded accesses.

> with MyObject do
> begin
> Lock;
> Method1;
> Method2;
> Unlock;
> end;

If Method1 or Method2 access shared resources that are not adequately
protected, then if another thread calls Method1 or Method2 while the
transaction is still running, the transaction can become broken as the
data can be altered unexpectedly at any time.

> In this case MyObject is clearly locked until both methods have been
> completed (because no other thread could gain control and unlock the
> object, assuming that every thread wraps any calls to object methods
in a
> Lock..Unlock construct).

If Method1 and Method2 do not call Lock/Unlock internally, then any
other thread can call them at any time while the object is locked, and
the operations inside of Method1 and Method2 will not be protected
correctly because they don't respect the lock. Forcing threads to
explicitally call Lock/Lock() before doing anything with the object is
well and good **IF** outside code behaves itself properly. But why
take the risk??? The object has to be locked anyway to ensure its
protection, so why not simply have it lock itself automatically? That
is MUCH safer, not only from a coding standpoint but from a design
standpoint as well. There is no excuse for not writing code that
protects itself from stupid programmers who don't follow the rules.

> Consider next alternative code that wraps local Lock..Unlock
constructs
> around each method call. The logic is equivalent to putting
Lock..Unlock
> constructs inside the method implementations.

Yes, it is the same model that is in play if you move the Lock/Unlock
calls inside Method1 and Method2. Which is where they belong in the
first place to ensure the integrity of the lock at all times. It is
safer and smarter programming practice to do so. Why are you fighting
against this so hard?

> Here the second Lock statement should have no effect, because
MyObject
> is already locked. However the first local Unlock statement will
unlock the
> object temporarily, opening up the opportunity for another waiting
thread to
> acquire control.

That is completely wrong. You have not read the documentation
carefully enough. The lock will remain active until the outer
Unlock() has been called. Synchronization objects are reference
counted. The keep track of how many times a thread has locked them.
That is why a thread can re-lock a synchronization object that has
already been locked by the same thread. Only the first call will
actually lock the object, setting its refrence count to 1, and then
the following calls will simply increment that count. That is why the
lock and unlock calls have to be balanced. Unlocking a lock
decrements its reference count, and it will not be fully released
until the count falls to zero. If you do not balance them correctly,
then any thread that tries to access the lock will become deadlocked.

> In this method MyObject will be unlocked each time a method is
exited

Again, that is completely not true. The object becomes locked when
the outer Lock() is called, and remains locked until the outer
Unlock() is called. The getter/setter methods that are called in
between will increment/decrement the reference count accordinly, but
the object will remain locked the entire time they are running.

> However there is the remote possibility that when it is unlocked
> (e.g. at the end of the MyObject.GetIntValue method), another thread
> could grab control before the first thread relocks the object.

No, there is not.

> Or is that impossible?

Yes, it is completely impossible, because the object is not actually
unlocked when you think it is. Your understanding of how
synchronization objects actually work is fundamentally inaccurate. I
strongly suggest that you re-read the documentation again.

> This shouldn't happen if the thread that wishes to use a single
method
> encloses the call in a MyObject.Lock..MyObject.Unlock construct.

That is correct. But that is just more work that each individual
thread's coding has to remember to perform. Why risk it? The
TMyObject instance can handle it automatically, and then the threads
don't have to bother with it at all.


Gambit


Enquiring Mind

unread,
Jan 28, 2007, 5:15:31 AM1/28/07
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:45bb43ec$1...@newsgroups.borland.com...

>
> If Method1 and Method2 do not call Lock/Unlock internally, then any
> other thread can call them at any time while the object is locked, and
> the operations inside of Method1 and Method2 will not be protected
> correctly because they don't respect the lock. Forcing threads to
> explicitally call Lock/Lock() before doing anything with the object is
> well and good **IF** outside code behaves itself properly. But why
> take the risk??? The object has to be locked anyway to ensure its
> protection, so why not simply have it lock itself automatically? That
> is MUCH safer, not only from a coding standpoint but from a design
> standpoint as well. There is no excuse for not writing code that
> protects itself from stupid programmers who don't follow the rules.
>
I fully agree with the safety benefits of incorporating the Lock.. Unlock
protection inside method implementations. The reason why I was unconvinced
about doing it, though, was that I was unsure how nested Lock..Unlock
constructs actually behave.

BTW, I don't look upon programmers who fail to follow the rules as being
necessarily stupid - often they are simply poorly informed, sometimes on
account of inadequate documentation, which is sadly too often the case when
it comes to classes and library routines, and sometimes because of
unnecessary complexity in the design of the class or class framework, or
maybe on account of limited experience. If only classes and frameworks were
defined with the same rigour that is used for defining the syntax and
semantics of programming languages such as Pascal and C#, programs would end
up being more reliable and easier to develop and maintain. It's probably
worth recalling that one of the strengths of the Pascal language is that it
was designed with the objective of minimising problems arising from the use
of the language by inexpert programmers.


>
> Yes, it is the same model that is in play if you move the Lock/Unlock
> calls inside Method1 and Method2. Which is where they belong in the
> first place to ensure the integrity of the lock at all times. It is
> safer and smarter programming practice to do so. Why are you fighting
> against this so hard?
>

I came up with the comparative examples in order to gain a better
understanding of how the TCriticalSection class actually behaves.

>> Here the second Lock statement should have no effect, because
> MyObject
>> is already locked. However the first local Unlock statement will
> unlock the
>> object temporarily, opening up the opportunity for another waiting
> thread to
>> acquire control.
>
> That is completely wrong. You have not read the documentation
> carefully enough. The lock will remain active until the outer
> Unlock() has been called. Synchronization objects are reference
> counted. The keep track of how many times a thread has locked them.
> That is why a thread can re-lock a synchronization object that has
> already been locked by the same thread. Only the first call will
> actually lock the object, setting its refrence count to 1, and then
> the following calls will simply increment that count. That is why the
> lock and unlock calls have to be balanced. Unlocking a lock
> decrements its reference count, and it will not be fully released
> until the count falls to zero. If you do not balance them correctly,
> then any thread that tries to access the lock will become deadlocked.
>

That's very useful! Thanks very much for clarifying that a TCriticalSection
instance is reference counted. I was not aware of this fact. I did'n see it
mentioned in the Delphi on-line help, so I expect it must be somewhere in
the Microsoft documentation. This really seems to completely resolve the
issue of how nested Lock..Unlock constructs work. If a TCriticalSection
object is reference counted, is there any need to explicitly destroy it, or
will it be destroyed automatically when it passes out of scope?

In conclusion, on the basis of the fact that CriticalSection objects are
reference counted, there can be little doubt that the best approach is to
individually protect every method implementation with an internal
Lock..Unlock construct, as you suggested in your previous post.

Regards,

Enquiring Mind

Remy Lebeau (TeamB)

unread,
Jan 28, 2007, 6:17:04 AM1/28/07
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in
message news:45bc782f$1...@newsgroups.borland.com...

> That's very useful! Thanks very much for clarifying that a
TCriticalSection
> instance is reference counted.

It is not TCriticalSection is that is reference counted. It is the
underlying critical section that it wraps. In fact, all OS-level sync
objects work this way. Once a thread has obtained the lock to such an
object (critical section, mutex, semaphore, event), it can re-lock the
object as many times as it wants. It just has to remember to release
the object each time it has entered it, in order to ensure that the
count will drop to zero and free the object for other threads to use.

> I was not aware of this fact. I did'n see it mentioned in the Delphi
on-line help

Again, this is not a Delphi issue to begin with. This is implemented
at the OS layer, and it fully documented by Microsoft. It is not
Borland's responsibility to duplicate what is already available
elsewhere. That is why Borland ships the Win32 API documentation with
its IDEs in the first place.

> I expect it must be somewhere in the Microsoft documentation.

The various sync objects have their own chapters in the API
documentation, complete with examples.

> If a TCriticalSection object is reference counted, is there any need
> to explicitly destroy it, or will it be destroyed automatically when
it
> passes out of scope?

That is not the kind of reference counting I was referring to. Yes,
you have to explicitally destroy it yourself when you are finished
with it. TCriticalSection is a VCL class, after all. It is not an
interface. All VCL objects have to be explicitally freed, and their
destructors will free the OS resources that the constructors
allocated. Just like any other class.


Gambit


Xavier

unread,
Jan 28, 2007, 7:14:31 AM1/28/07
to
Remy Lebeau (TeamB) wrote:
> TCriticalSection is a VCL class, after all. It is not an
> interface. All VCL objects have to be explicitally freed, and their
> destructors will free the OS resources that the constructors
> allocated. Just like any other class.

I understand what you meant, of course, but strictly speaking,
TCriticalSection is in SyncObjs.pas, part of the RTL; the source is
clearly separated from the VCL.

Calling TCriticalSection a VCL class is wrong on several levels: it's
not in the library, it doesn't depend on the library, and it's not
something you can drop in the designer.

Your last sentence summed it up nicely: "Just like any other class."
The talk about VCL classes and objects is just added confusion, IMHO.

0 new messages