123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- {
- 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.
- **********************************************************************}
- {$ifdef FPC_HAS_FEATURE_THREADING}
- 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;
- type
- PMREWThreadInfo = ^TMREWThreadInfo;
- TMREWThreadInfo = record
- Next: PMREWThreadInfo;
- Active: longint;
- RefCount: LongInt;
- ThreadID: TThreadID;
- end;
- const
- cInUse: LongInt = MaxInt;
- cAvail: LongInt = 0;
- const
- cNewReader : LongInt = - $1;
- cNewWriter : LongInt = $10000;
- cReadMask : LongInt = $0000FFFF;
- cWriteMask : LongInt = $7FFF0000;
- constructor TMultiReadExclusiveWriteSynchronizer.Create;
- begin
- System.InitCriticalSection(fwritelock);
- fwaitingwriterlock:=RTLEventCreate;
- RTLEventResetEvent(fwaitingwriterlock);
- fwriterequests:=0;
- factivethreads:=0;
- freaderqueue:=BasicEventCreate(nil,true,false,'');
- { synchronize initialization with later reads/writes }
- ReadWriteBarrier;
- end;
- destructor TMultiReadExclusiveWriteSynchronizer.Destroy;
- var
- p,q: PMREWThreadInfo;
- i: integer;
- begin
- System.DoneCriticalSection(fwritelock);
- RtlEventDestroy(fwaitingwriterlock);
- BasicEventDestroy(freaderqueue);
- { Clean up thread info }
- for i:=Low(fThreadList) to High(fThreadList) do
- begin
- q:=fThreadList[i];
- fThreadList[i]:=nil;
- while q<>nil do
- begin
- p:=q;
- q:=q^.Next;
- FreeMem(p);
- end;
- end;
- end;
- function TMultiReadExclusiveWriteSynchronizer.Beginwrite : boolean;
- var
- p: PMREWThreadInfo;
- begin
- { Result indicates whether the protected memory was not modified,
- that is, it is set to false if another writer could have chagned
- memory}
- Result:=True;
- { 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 }
- { Count pending not granted requests so writes always take priority over
- read requests}
- System.InterlockedIncrement(fwriterequests);
- { Get per thread counter }
- p:=PMREWThreadInfo( GetThreadInfo(True) );
- if System.TryEnterCriticalSection(fwritelock)=0 then
- begin
- { TryEnterCriticalSection failed, so not first in the write lock queue }
- Result:=False;
- { If we hold a read lock, then a deadlock will result.. This is because
- the first thread in the write queue (holding fwritelock) does not have
- mutexes write lock, as it must be waiting on this thread to release
- its' read lock }
- if p^.RefCount > 0 then
- begin
- System.InterlockedDecrement(fwriterequests);
- raise TMREWException.Create('Deadlock detected');
- end;
- { wait for any other writers that may be in progress }
- System.EnterCriticalSection(fwritelock);
- end;
- { Need to synchronize with readers only when this is the first
- write request by this thread }
- if (p^.RefCount and cWriteMask)=0 then
- begin
- { Count active threads rather than readers. A write request can
- be granted whenever the count has reduced to one }
- { no order vs increment of fwriterequests needed, because we acquired
- a critical section which orders for us }
- if p^.RefCount=0 then
- System.InterlockedIncrement(factivethreads);
- { 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);
- { 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);
- { wait until we are the only active thread (no need for memory order
- barriers, because RTLeventResetEvent and RTLEventWaitFor imply a
- full barrier) }
- while System.InterlockedExchangeAdd(factivethreads,0) > 1 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;
- end;
- { Count write lock acquisitions by thread }
- Inc(p^.RefCount,cNewWriter);
- end;
- procedure TMultiReadExclusiveWriteSynchronizer.Endwrite;
- var
- p: PMREWThreadInfo;
- begin
- p:=PMREWThreadInfo( GetThreadInfo(False) );
- { Protect against EndWrite called out of sequence blocking lock }
- if (p<>nil) and
- ((p^.RefCount and cWriteMask)<>0) then
- begin
- { Update per thread counter before releasing next write thread }
- Dec(p^.RefCount,cNewWriter);
- { Finish all writes inside the section, and the update of the RefCount,
- so that everything executing afterwards will certainly see these
- results }
- WriteBarrier;
- { Reduce active thread count assuming it was not previously a read lock }
- if p^.RefCount=0 then
- begin
- System.InterlockedDecrement(factivethreads);
- { order w.r.t. decrement of fwriterequests below }
- WriteBarrier;
- end;
- { signal potential readers that the coast is clear if all recursive
- write locks have been freed }
- { Also test for pending write requests }
- if System.InterlockedDecrement(fwriterequests)=0 then
- begin
- { No more writers pending, wake any pending readers }
- BasicEventSetEvent(freaderqueue);
- end;
- { free the writer lock so another writer can become active }
- System.LeaveCriticalSection(fwritelock);
- { Remove reference to thread if not in use any more }
- if p^.RefCount=0 then
- RemoveThread(p);
- end
- else
- raise TMREWException.Create('EndWrite called before BeginWrite');
- end;
- procedure TMultiReadExclusiveWriteSynchronizer.Beginread;
- Const
- wrSignaled = 0;
- wrTimeout = 1;
- wrAbandoned= 2;
- wrError = 3;
- var
- p: PMREWThreadInfo;
- begin
- { Check if we already have a lock, if so grant immediate access }
- p:=PMREWThreadInfo( GetThreadInfo(True) );
- if p^.RefCount=0 then
- begin
- { Wanted non-recursive read lock, so increase active threads }
- System.InterlockedIncrement(factivethreads);
- { wait until there are no more writer active or pending }
- ReadWriteBarrier;
- while System.InterlockedExchangeAdd(fwriterequests,0)<>0 do
- begin
- ReadWriteBarrier;
- { This thread is not active }
- if System.InterlockedDecrement(factivethreads)<>0 then
- RTLEventSetEvent(fwaitingwriterlock);
- if (BasicEventWaitFor(high(cardinal),freaderqueue) in [wrAbandoned,wrError]) then
- raise TMREWException.create('BasicEventWaitFor failed in TMultiReadExclusiveWriteSynchronizer.Beginread');
- { Try again to make this thread active }
- System.InterlockedIncrement(factivethreads);
- { order w.r.t. reading fwriterequests }
- ReadWriteBarrier;
- end;
- { Make sure that out-of-order execution cannot perform reads
- inside the critical section before the lock has been acquired }
- ReadBarrier;
- end;
- { Count read lock acquisitions by thread: Inc(p^.RefCount,cNewReader) }
- Inc(p^.RefCount);
- end;
- procedure TMultiReadExclusiveWriteSynchronizer.Endread;
- var
- p: PMREWThreadInfo;
- begin
- p:=PMREWThreadInfo( GetThreadInfo(False) );
- if (p<>nil) and
- ((p^.RefCount and cReadMask)<>0) then
- begin
- { Update per thread counter: Dec(p^.RefCount,cNewReader) }
- Dec(p^.RefCount);
- { if this is the last recursive call }
- if p^.RefCount=0 then
- begin
- { Thread no longer has an active lock }
- { If no more readers, wake writer in the ready-queue if any.
- Every queued thread requesting a write lock increments fwriterequests,
- and the first queued writer thread checks factivethreads is active
- (will be set already during lock promotion) }
- if System.InterlockedDecrement(factivethreads)=1 then
- begin
- { order w.r.t. access to factivethreads }
- ReadBarrier;
- if fwriterequests>0 then
- RTLEventSetEvent(fwaitingwriterlock);
- end;
- { remove reference to this thread }
- RemoveThread(p);
- end;
- end
- else
- raise TMREWException.Create('EndRead called before BeginRead');
- end;
- function TMultiReadExclusiveWriteSynchronizer.ThreadIDtoIndex(aThreadID: TThreadID): integer;
- begin
- Result:=
- (
- ptruint(aThreadID) xor (ptruint(aThreadID) shr 12)
- {$ifdef cpu64}
- xor (ptruint(aThreadID) shr 32) xor (ptruint(aThreadID) shr 36) xor (ptruint(aThreadID) shr 48)
- {$endif}
- ) and $FFFF;
- Result:=(Result xor (Result shr 4)) and $0F; // Return range 0..15
- end;
- function TMultiReadExclusiveWriteSynchronizer.GetThreadInfo(AutoCreate: Boolean): Pointer;
- var
- p: PMREWThreadInfo;
- AThreadID: TThreadID;
- FreeSlot: Boolean;
- OldState: LongInt;
- Index: integer;
- begin
- FreeSlot:=False;
- AThreadID:=ThreadID;
- Index:=ThreadIDtoIndex( AThreadID );
- p:=PMREWThreadInfo(fThreadList[Index]);
- while (p<>nil) and
- (p^.ThreadID<>AThreadID) do
- begin
- if p^.Active=cAvail then // Is slot available for use
- FreeSlot:=True; // Yes, remember in case we need it as this is a new thread
- p:=p^.Next;
- ReadBarrier;
- end;
- if p=nil then
- begin
- { count threads with locks }
- if FreeSlot then
- begin
- p:=fThreadList[Index];
- while (p<>nil) do
- begin
- if p^.Active=cAvail then
- begin
- OldState:=InterlockedExchange( p^.Active, cInUse );
- if OldState=cAvail then
- begin
- p^.ThreadID:=AThreadID; // Tag to thread
- Break;
- end;
- end;
- p:=p^.Next;
- ReadBarrier;
- end;
- end;
- if p=nil then
- begin
- p:=PMREWThreadInfo(AllocMem(SizeOf(TMREWThreadInfo)));
- p^.ThreadID:=AThreadID;
- p^.RefCount:=0;
- p^.Active:=cInUse;
- { Now insert into the chain header }
- p^.Next:=p;
- WriteBarrier;
- { other threads will spin (loop) after the InterlockedExchange
- until the field "next" is written, then this node is first in
- the forward linked list }
- p^.Next:=System.InterlockedExchange(fThreadList[Index],p);
- end;
- end;
- Result:=p;
- end;
- procedure TMultiReadExclusiveWriteSynchronizer.RemoveThread(AThreadInfo: Pointer);
- var
- p: PMREWThreadInfo;
- begin
- p:=PMREWThreadInfo(AThreadInfo);
- if p<>nil then
- begin
- { Prevent matching during GetThreadInfo }
- p^.ThreadID:=tthreadid(-1);
- WriteBarrier;
- { Mark slot available }
- p^.Active:=cAvail;
- end;
- end;
- {$endif FPC_HAS_FEATURE_THREADING}
|