fpc/rtl/objpas/sysutils/sysuthrd.inc
Jonas Maebe d5f415b047 * initialise fwritelocked and freadercount in the TMREWS constructor using
atomic operations so that in case it's created while multiple threads are
    already running, all threads are guaranteed to see this initialisation
  * some minor changes to the comments of TMREWS

git-svn-id: trunk@15074 -
2010-03-27 12:44:51 +00:00

167 lines
5.8 KiB
PHP

{
This file is part of the Free Pascal run time library.
Copyright (c) 2005,2009 by the Free Pascal development team
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
constructor TSimpleRWSync.Create;
begin
System.InitCriticalSection(Crit);
end;
destructor TSimpleRWSync.Destroy;
begin
System.DoneCriticalSection(Crit);
end;
function TSimpleRWSync.Beginwrite : boolean;
begin
System.EnterCriticalSection(Crit);
result:=true;
end;
procedure TSimpleRWSync.Endwrite;
begin
System.LeaveCriticalSection(Crit);
end;
procedure TSimpleRWSync.Beginread;
begin
System.EnterCriticalSection(Crit);
end;
procedure TSimpleRWSync.Endread;
begin
System.LeaveCriticalSection(Crit);
end;
constructor TMultiReadExclusiveWriteSynchronizer.Create;
begin
System.InitCriticalSection(fwritelock);
fwaitingwriterlock:=RTLEventCreate;
RTLEventResetEvent(fwaitingwriterlock);
{ make sure all threads see the initialisation of fwritelock and
freadercount (we only use atomic operation further on as well) }
System.InterlockedExchange(freadercount,0);
System.InterlockedExchange(fwritelocked,0);
freaderqueue:=BasicEventCreate(nil,true,false,'');
end;
destructor TMultiReadExclusiveWriteSynchronizer.Destroy;
begin
System.InterlockedExchange(fwritelocked,0);
System.DoneCriticalSection(fwritelock);
RtlEventDestroy(fwaitingwriterlock);
BasicEventDestroy(freaderqueue);
end;
function TMultiReadExclusiveWriteSynchronizer.Beginwrite : boolean;
begin
{ wait for any other writers that may be in progress }
System.EnterCriticalSection(fwritelock);
{ it is possible that earlier on we missed waiting on the
fwaitingwriterlock and that it's still set (must be done
after acquiring the fwritelock, because otherwise one
writer could reset the fwaitingwriterlock of another one
that's about to wait on it) }
RTLeventResetEvent(fwaitingwriterlock);
{ new readers have to block from now on; writers get priority to avoid
writer starvation (since they have to compete with potentially many
concurrent readers and other writers) }
BasicEventResetEvent(freaderqueue);
{ for quick checking by candidate-readers -- use interlockedincrement/
decrement instead of setting 1/0, because a single thread can
recursively acquire the write lock multiple times }
System.InterlockedIncrement(fwritelocked);
{ wait until all readers are gone -- freadercount and fwritelocked are only
accessed using atomic operations (that's why we use
InterLockedExchangeAdd(x,0) below) -> always in-order. The writer always
first sets fwritelocked and then checks freadercount, while the readers
always first increase freadercount and then check fwritelocked }
while (System.InterLockedExchangeAdd(freadercount,0)<>0) do
RTLEventWaitFor(fwaitingwriterlock);
{ Make sure that out-of-order execution cannot already perform reads
inside the critical section before the lock has been acquired }
ReadBarrier;
{ we have the writer lock, and all readers are gone }
result:=true;
end;
procedure TMultiReadExclusiveWriteSynchronizer.Endwrite;
begin
{ Finish all writes inside the section so that everything executing
afterwards will certainly see these results }
WriteBarrier;
{ signal potential readers that the coast is clear if all recursive
write locks have been freed }
if System.InterlockedDecrement(fwritelocked)=0 then
begin
{ wake up waiting readers (if any); do not check first whether freadercount
is <> 0, because the InterlockedDecrement in the while loop of BeginRead
can have already occurred, so a single reader may be about to wait on
freaderqueue even though freadercount=0. Setting an event multiple times
is no problem. }
BasicEventSetEvent(freaderqueue);
end;
{ free the writer lock so another writer can become active }
System.LeaveCriticalSection(fwritelock);
end;
procedure TMultiReadExclusiveWriteSynchronizer.Beginread;
Const
wrSignaled = 0;
wrTimeout = 1;
wrAbandoned= 2;
wrError = 3;
begin
System.InterlockedIncrement(freadercount);
{ wait until there is no more writer }
while System.InterLockedExchangeAdd(fwritelocked,0)<>0 do
begin
{ there's a writer busy or wanting to start -> wait until it's
finished; a writer may already be blocked in the mean time, so
wake it up if we're the last to go to sleep }
if System.InterlockedDecrement(freadercount)=0 then
RTLEventSetEvent(fwaitingwriterlock);
if (BasicEventWaitFor(high(cardinal),freaderqueue) in [wrAbandoned,wrError]) then
raise Exception.create('BasicEventWaitFor failed in TMultiReadExclusiveWriteSynchronizer.Beginread');
{ and try again: first increase freadercount, only then check
fwritelocked }
System.InterlockedIncrement(freadercount);
end;
{ Make sure that out-of-order execution cannot perform reads
inside the critical section before the lock has been acquired }
ReadBarrier;
end;
procedure TMultiReadExclusiveWriteSynchronizer.Endread;
begin
{ If no more readers, wake writer in the ready-queue if any. Since a writer
always first atomically sets fwritelocked and then atomically checks the
freadercount, first modifying freadercount and then checking fwritelock
ensures that we cannot miss one of the events regardless of execution
order. }
if (System.InterlockedDecrement(freadercount)=0) and
(System.InterLockedExchangeAdd(fwritelocked,0)<>0) then
RTLEventSetEvent(fwaitingwriterlock);
end;