mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-08-15 09:09:09 +02:00
* optimized version of TMultiReadExclusiveWriteSynchronizer that allows
concurrent readers (mantis #14451) git-svn-id: trunk@14587 -
This commit is contained in:
parent
b8d5a8f44c
commit
106baa8b2f
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -9421,6 +9421,7 @@ tests/test/units/sysutils/tfile2.pp svneol=native#text/plain
|
|||||||
tests/test/units/sysutils/tfilename.pp svneol=native#text/plain
|
tests/test/units/sysutils/tfilename.pp svneol=native#text/plain
|
||||||
tests/test/units/sysutils/tfloattostr.pp svneol=native#text/plain
|
tests/test/units/sysutils/tfloattostr.pp svneol=native#text/plain
|
||||||
tests/test/units/sysutils/tlocale.pp svneol=native#text/plain
|
tests/test/units/sysutils/tlocale.pp svneol=native#text/plain
|
||||||
|
tests/test/units/sysutils/trwsync.pp svneol=native#text/plain
|
||||||
tests/test/units/sysutils/tsscanf.pp svneol=native#text/plain
|
tests/test/units/sysutils/tsscanf.pp svneol=native#text/plain
|
||||||
tests/test/units/sysutils/tstrtobool.pp svneol=native#text/plain
|
tests/test/units/sysutils/tstrtobool.pp svneol=native#text/plain
|
||||||
tests/test/uobjc24.pp svneol=native#text/plain
|
tests/test/uobjc24.pp svneol=native#text/plain
|
||||||
|
@ -21,7 +21,7 @@ type
|
|||||||
procedure EndWrite;
|
procedure EndWrite;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMultiReadExclusiveWriteSynchronizer = class(TInterfacedObject,IReadWriteSync)
|
TSimpleRWSync = class(TInterfacedObject,IReadWriteSync)
|
||||||
private
|
private
|
||||||
crit : TRtlCriticalSection;
|
crit : TRtlCriticalSection;
|
||||||
public
|
public
|
||||||
@ -33,3 +33,18 @@ type
|
|||||||
procedure Endread;
|
procedure Endread;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TMultiReadExclusiveWriteSynchronizer = class(TInterfacedObject,IReadWriteSync)
|
||||||
|
private
|
||||||
|
freaderqueue: peventstate;
|
||||||
|
fwritelock,
|
||||||
|
fwaitingwriterlock: prtlevent;
|
||||||
|
freadercount: cardinal;
|
||||||
|
fwritelocked: longint;
|
||||||
|
public
|
||||||
|
constructor Create; virtual;
|
||||||
|
destructor Destroy; override;
|
||||||
|
function Beginwrite : boolean;
|
||||||
|
procedure Endwrite;
|
||||||
|
procedure Beginread;
|
||||||
|
procedure Endread;
|
||||||
|
end;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
This file is part of the Free Pascal run time library.
|
This file is part of the Free Pascal run time library.
|
||||||
Copyright (c) 2005 by the Free Pascal development team
|
Copyright (c) 2005,2009 by the Free Pascal development team
|
||||||
|
|
||||||
See the file COPYING.FPC, included in this distribution,
|
See the file COPYING.FPC, included in this distribution,
|
||||||
for details about the copyright.
|
for details about the copyright.
|
||||||
@ -12,33 +12,146 @@
|
|||||||
**********************************************************************}
|
**********************************************************************}
|
||||||
|
|
||||||
|
|
||||||
constructor TMultiReadExclusiveWriteSynchronizer.Create;
|
constructor TSimpleRWSync.Create;
|
||||||
begin
|
begin
|
||||||
System.InitCriticalSection(Crit);
|
System.InitCriticalSection(Crit);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TMultiReadExclusiveWriteSynchronizer.Destroy;
|
destructor TSimpleRWSync.Destroy;
|
||||||
begin
|
begin
|
||||||
System.DoneCriticalSection(Crit);
|
System.DoneCriticalSection(Crit);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMultiReadExclusiveWriteSynchronizer.Beginwrite : boolean;
|
function TSimpleRWSync.Beginwrite : boolean;
|
||||||
begin
|
begin
|
||||||
System.EnterCriticalSection(Crit);
|
System.EnterCriticalSection(Crit);
|
||||||
result:=true;
|
result:=true;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMultiReadExclusiveWriteSynchronizer.Endwrite;
|
procedure TSimpleRWSync.Endwrite;
|
||||||
begin
|
begin
|
||||||
System.LeaveCriticalSection(Crit);
|
System.LeaveCriticalSection(Crit);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMultiReadExclusiveWriteSynchronizer.Beginread;
|
procedure TSimpleRWSync.Beginread;
|
||||||
begin
|
begin
|
||||||
System.EnterCriticalSection(Crit);
|
System.EnterCriticalSection(Crit);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMultiReadExclusiveWriteSynchronizer.Endread;
|
procedure TSimpleRWSync.Endread;
|
||||||
begin
|
begin
|
||||||
System.LeaveCriticalSection(Crit);
|
System.LeaveCriticalSection(Crit);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
constructor TMultiReadExclusiveWriteSynchronizer.Create;
|
||||||
|
begin
|
||||||
|
fwritelock:=RTLEventCreate;
|
||||||
|
RTLeventSetEvent(fwritelock);
|
||||||
|
fwaitingwriterlock:=RTLEventCreate;
|
||||||
|
RTLEventResetEvent(fwaitingwriterlock);
|
||||||
|
fwritelocked:=0;
|
||||||
|
freadercount:=0;
|
||||||
|
freaderqueue:=BasicEventCreate(nil,true,false,'');
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
destructor TMultiReadExclusiveWriteSynchronizer.Destroy;
|
||||||
|
begin
|
||||||
|
InterlockedExchange(fwritelocked,0);
|
||||||
|
RtlEventDestroy(fwritelock);
|
||||||
|
BasicEventDestroy(freaderqueue);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TMultiReadExclusiveWriteSynchronizer.Beginwrite : boolean;
|
||||||
|
begin
|
||||||
|
{ wait for any other writers that may be in progress }
|
||||||
|
RTLEventWaitFor(fwritelock);
|
||||||
|
{ it is possible that we earlier on missed waiting on the
|
||||||
|
fwaitingwriterlock and that it's still set (must be done
|
||||||
|
after aquiring the fwritelock, because otherwise one
|
||||||
|
writer could reset the fwaitingwriterlock of another one
|
||||||
|
that's about to wait on it) }
|
||||||
|
BasicEventResetEvent(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) }
|
||||||
|
BasicEventResetEvent(freaderqueue);
|
||||||
|
{ for quick checking by candidate-readers }
|
||||||
|
InterlockedExchange(fwritelocked,1);
|
||||||
|
|
||||||
|
{ 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 (InterLockedExchangeAdd(freadercount,0)<>0) do
|
||||||
|
RTLEventWaitFor(fwaitingwriterlock);
|
||||||
|
|
||||||
|
{ 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 }
|
||||||
|
InterlockedExchange(fwritelocked,0);
|
||||||
|
{ 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);
|
||||||
|
{ free the writer lock so another writer can become active }
|
||||||
|
RTLeventSetEvent(fwritelock);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMultiReadExclusiveWriteSynchronizer.Beginread;
|
||||||
|
Const
|
||||||
|
wrSignaled = 0;
|
||||||
|
wrTimeout = 1;
|
||||||
|
wrAbandoned= 2;
|
||||||
|
wrError = 3;
|
||||||
|
begin
|
||||||
|
InterlockedIncrement(freadercount);
|
||||||
|
{ wait until there is no more writer }
|
||||||
|
while InterLockedExchangeAdd(fwritelocked,0)<>0 do
|
||||||
|
begin
|
||||||
|
{ there's a writer busy or ir 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 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 }
|
||||||
|
InterlockedIncrement(freadercount);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMultiReadExclusiveWriteSynchronizer.Endread;
|
||||||
|
begin
|
||||||
|
{ Make sure that all read operations have finished, so that none of those
|
||||||
|
can still be executed after a writer starts working and changes some
|
||||||
|
things }
|
||||||
|
ReadBarrier;
|
||||||
|
|
||||||
|
{ If no more readers, wake writer in the ready-queue if any. Since a writer
|
||||||
|
always first atomically sets the 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 (InterlockedDecrement(freadercount)=0) and
|
||||||
|
(InterLockedExchangeAdd(fwritelocked,0)<>0) then
|
||||||
|
RTLEventSetEvent(fwaitingwriterlock);
|
||||||
|
end;
|
||||||
|
146
tests/test/units/sysutils/trwsync.pp
Normal file
146
tests/test/units/sysutils/trwsync.pp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{$ifdef fpc}
|
||||||
|
{$mode objfpc}
|
||||||
|
{$h+}
|
||||||
|
{$endif}
|
||||||
|
|
||||||
|
uses
|
||||||
|
{$ifdef unix}
|
||||||
|
cthreads,
|
||||||
|
{$endif}
|
||||||
|
SysUtils, Classes;
|
||||||
|
|
||||||
|
var
|
||||||
|
lock: TMultiReadExclusiveWriteSynchronizer;
|
||||||
|
gcount: longint;
|
||||||
|
|
||||||
|
type
|
||||||
|
tcounter = class(tthread)
|
||||||
|
private
|
||||||
|
flock: TMultiReadExclusiveWriteSynchronizer;
|
||||||
|
flocalcount: longint;
|
||||||
|
public
|
||||||
|
constructor create;
|
||||||
|
property localcount: longint read flocalcount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
treadcounter = class(tcounter)
|
||||||
|
procedure execute; override;
|
||||||
|
end;
|
||||||
|
|
||||||
|
twritecounter = class(tcounter)
|
||||||
|
procedure execute; override;
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor tcounter.create;
|
||||||
|
begin
|
||||||
|
{ create suspended }
|
||||||
|
inherited create(true);
|
||||||
|
freeonterminate:=false;
|
||||||
|
flock:=lock;
|
||||||
|
flocalcount:=0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure treadcounter.execute;
|
||||||
|
var
|
||||||
|
i: longint;
|
||||||
|
l: longint;
|
||||||
|
r: longint;
|
||||||
|
begin
|
||||||
|
for i:=1 to 100000 do
|
||||||
|
begin
|
||||||
|
lock.beginread;
|
||||||
|
inc(flocalcount);
|
||||||
|
l:=gcount;
|
||||||
|
if (random(10000)=0) then
|
||||||
|
sleep(20);
|
||||||
|
{ this must cause data races/loss at some point }
|
||||||
|
gcount:=l+1;
|
||||||
|
lock.endread;
|
||||||
|
r:=random(30000);
|
||||||
|
if (r=0) then
|
||||||
|
sleep(30);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure twritecounter.execute;
|
||||||
|
var
|
||||||
|
i: longint;
|
||||||
|
l: longint;
|
||||||
|
r: longint;
|
||||||
|
begin
|
||||||
|
for i:=1 to 500 do
|
||||||
|
begin
|
||||||
|
lock.beginwrite;
|
||||||
|
inc(flocalcount);
|
||||||
|
l:=gcount;
|
||||||
|
if (random(100)=0) then
|
||||||
|
sleep(20);
|
||||||
|
{ we must be exclusive }
|
||||||
|
if gcount<>l then
|
||||||
|
halt(1);
|
||||||
|
gcount:=l+1;
|
||||||
|
lock.endwrite;
|
||||||
|
r:=random(30);
|
||||||
|
if (r>28) then
|
||||||
|
sleep(r);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
var
|
||||||
|
r1,r2,r3,r4,r5,r6: treadcounter;
|
||||||
|
w1,w2,w3,w4: twritecounter;
|
||||||
|
begin
|
||||||
|
randomize;
|
||||||
|
lock:=TMultiReadExclusiveWriteSynchronizer.create;
|
||||||
|
{ first try some writers }
|
||||||
|
w1:=twritecounter.create;
|
||||||
|
w2:=twritecounter.create;
|
||||||
|
w3:=twritecounter.create;
|
||||||
|
w4:=twritecounter.create;
|
||||||
|
w1.resume;
|
||||||
|
w2.resume;
|
||||||
|
w3.resume;
|
||||||
|
w4.resume;
|
||||||
|
w1.waitfor;
|
||||||
|
w2.waitfor;
|
||||||
|
w3.waitfor;
|
||||||
|
w4.waitfor;
|
||||||
|
|
||||||
|
{ must not have caused any data races }
|
||||||
|
if (gcount<>w1.localcount+w2.localcount+w3.localcount+w4.localcount) then
|
||||||
|
halt(1);
|
||||||
|
|
||||||
|
{ now try some mixed readers/writers }
|
||||||
|
gcount:=0;
|
||||||
|
r1:=treadcounter.create;
|
||||||
|
r2:=treadcounter.create;
|
||||||
|
r3:=treadcounter.create;
|
||||||
|
r4:=treadcounter.create;
|
||||||
|
r5:=treadcounter.create;
|
||||||
|
r6:=treadcounter.create;
|
||||||
|
w1:=twritecounter.create;
|
||||||
|
w2:=twritecounter.create;
|
||||||
|
|
||||||
|
r1.resume;
|
||||||
|
r2.resume;
|
||||||
|
r3.resume;
|
||||||
|
r4.resume;
|
||||||
|
r5.resume;
|
||||||
|
r6.resume;
|
||||||
|
w1.resume;
|
||||||
|
w2.resume;
|
||||||
|
|
||||||
|
r1.waitfor;
|
||||||
|
r2.waitfor;
|
||||||
|
r3.waitfor;
|
||||||
|
r4.waitfor;
|
||||||
|
r5.waitfor;
|
||||||
|
r6.waitfor;
|
||||||
|
w1.waitfor;
|
||||||
|
w2.waitfor;
|
||||||
|
|
||||||
|
{ updating via the readcount must have caused data races }
|
||||||
|
if (gcount>=r1.localcount+r2.localcount+r3.localcount+r4.localcount+r5.localcount+r6.localcount+w1.localcount+w2.localcount) then
|
||||||
|
halt(2);
|
||||||
|
end.
|
Loading…
Reference in New Issue
Block a user