[All]
Working with TMultiReadExclusiveWriteSynchronizer
By: Justin Swett
Abstract: Using TMultiReadExclusiveWriteSynchronizer in Multi Thread Applications
Working with TMultiReadExclusiveWriteSynchronizer
By Justin Swett
Delphi Developer Support
There will undoubtedly come a time when the
need for developing a multi threaded application is a necessary. In some
cases the use of a TMultiReadExclusiveWriteSynchronizer (TMREWS for short)
will improve the performance of your threads, and therefore improve the
performance of your application. (As a parenthetical side note, don't you just
love saying "TMultiReadExclusiveWriteSynchronizer!") This article will cover the basics of
using a TMREWS and point out some not so obvious pitfalls to avoid. You can
download the complete project here.
|
When to use a TMultiReadExclusiveWriteSynchronizer
As the name of the component implies, a TMREWS should be used when there
is much reading and little writing to be done. In addition, the READ operations should
be kept to a MINIMUM otherwise your write threads could be left waiting indefinitely.
If there are going to be equal amounts of READING and WRITING then you might want
to consider using critical sections instead.
|
Creating a Thread Class that uses the TMREWS
One of the first things to do is declare a TMREWS. If you downloaded the complete project,
you'll see a global lock declared inside the implementation section of the thread
unit. It doesn't matter where the lock is declared so long as the following criteria are met:
- Threads can access the lock.
- The lock has been instantiated.
(Use initialization section and finalization for this)
- Threads can access memory that you are trying to read/write to.
Once the lock has been declared you can create your thread class. You will need to override
the Execute procedure, which is where you will be using TMREWS lock. Here is an example
of an execute procedure using the TMREWS:
procedure TWorkerThread.Execute;
begin
{ Place thread code here }
while not Terminated do
begin
//Updates form that thread is "sleeping"
Synchronize(Sleeping);
//Sleep thread momentarily, for illustrative purposes
Pause;
//Update form that thread is "waiting"
Synchronize(Waiting);
//check to make sure thread hasn't been terminated
if Terminated then exit;
//if reader then
if (fThreadType = ttRead) and (not Terminated) then
begin
globalLock.BeginRead;
//wrap endRead in try finally... otherwise deadlock could occur
try
Synchronize(Reading); //Still important to synchronize VCL calls
Pause; //represents thread work being done
Synchronize(DoneReading);
finally
globalLock.EndRead;
end;
end
//if writer then
else if (fThreadType = ttWrite) and (not Terminated) then
begin
globalLock.BeginWrite;
//wrap in a try finally to prevent deadlock
try
Synchronize(Writing);
Pause; // represents data manipulation
Synchronize(DoneWriting);
finally
globalLock.EndWrite;
end
else
exit;
end;
end;
Notice that the work done after Begin'X' is wrapped in a try finally. This ensures that that the appropriate End'X' will get called if any exceptions are raised . If an exception is raised then other threads might not be able obtain locks. If there is no chance of an exception being raised, then removing the try finally for performance reasons would be acceptable.
Make sure any memory being referenced by your threads is sandwiched with calls
to the TMREWS BeginREAD/WRITE and EndREAD/WRITE, otherwise your work of making your data
thread safe
is forfeited.
|
"Lock it up Chris, I'm about to..."
TMREWS allows you to promote a READ lock to a WRITE lock if the same thread were to make such a
request. Although this can be advantageous it can also cause two threads to lock up on each other. For example,
two instances of a thread type are instantiated with and Execute method which promotes a read
lock to a write lock. If the first thread calls BeginWrite after the second thread has called BeginWrite
then the first thread is blocked. The second thread will at some point attempt to call BeginWrite,
which will also get blocked. Now the two threads are waiting for the other to release the lock.
Another situation will arise where several Read threads are running and perhaps just a few Write threads.
When ever a Read thread calls BeginRead a Read lock is granted... so long as there isn't a Write
lock currently out. If a Write thread were to call BeginWrite while a Read thread had the lock
it could potentially be wait indefinitely, while other read threads obtain Read locks. Requests
for locks are not queued, so this isn't a bug and is in fact as designed.
|
Summary
Using the TMultiReadExclusiveWriteSynchronizer (just had to say it one more time) is fairly simple.
To use them successfully you should call BeginRead/Write matched with the appropriate EndRead/Write.
You should also check you read locks out for as little time as possible if you want your write threads to
complete their execution.
|
Connect with Us