I think your algorithm has one "whole". Follow the scenario:
Thread "W" begins the process of acquiring the "Write" lock.
Somewhere around the "mov ECX,-1" it's time slice ends up.
Thread "R" begins the process of acquiring the "Read" lock.
If it gets past the "JS @@Spin" there's nothing to stop both
threads from getting the lock. What's worst is that once the
lock is acquired by both threads, the lock gets threshed.
When the "W" threads finishes it's job it will set the value
to "0". When the "R" threads finishes it will actually set
the value to "-1". At this point the lock is locked for ever...
The "whole" comes from the fact you're using the spin lock
for more than locking. Classic spin-lock algorithms use one
fixed value for "locked" and one fixed value for "open", so
it's guaranteed that only one thread will ever acquire the
lock. In the locked exchange the classic spin-lock algorithm
always writes the "LOCKED" value when it tries to acquire the
lock. Next it can safely test for "UNLOCKED". If it didn't
get "UNLOCKED" then it may safely loop on because it's
guaranteed that it replaced "LOCKED" with "LOCKED" - so it
did no harm.
I think you should change your algorithm to use two separate
variables: one for the lock, one for the counter. Protect the
"counter" with the "lock" so you're guaranteed increments work
right and you don't trash a write lock.
P.S: Why didn't you go with the TMultiReadExclusiveWriteSynchronizer?
Note this is not a recommendation to use that, I have no idea
how efficient it actually is - I just know it's there and it
does what you need.
--
Cosmin Prund
If it gets past the "JS @@Spin" there's nothing to stop
both threads from getting the lock.
Well, actually only one thread can acquire the lock in your scenario.
Notice that both __Sync() methods uses CMPXCHG methods, which basically
ensures the value in the "counter" is the same as in EAX (which is a
copy of the "counter" value) before trying to modify it.
If the reader thread executes CMPXCHG first, the counter becomes "1",
then when the writer thread executes CMPXCHG, the "-1" will not be written
to the counter, becuase it compares "1" (in counter) with "0" (in EAX);
If the writer thread executes CMPXCHG first, the counter becomes "-1",
then when the reader thread executes CMPXCHG, the "1" will not be written
to the counter, becuase it compares "-1" (in counter) with "0" (in EAX);
Becuase both COMXCHG asserts LOCK, parallel execution of both cannot
happen.
However, I do have an amendment to my last email:
The recommended usage for dereferencing should be as follows,
TRWSpinLock.WriteSync(RWCounter);
Pointer(TempIntf):= Pointer(ProtectedIntf);
Pointer(ProtectedIntf):= NIL;
TRWSpinLock.WriteDone;
TempIntf:= NIL;
The reason of doing so is to enforce the "dereference" operation to be
"very very short". If refcount maintaining happens inside the writer lock
destructor of the object may be invoked, which may take a long time
(such as sleep or waiting for some objects); and, because this lock spins
endlessly until acquired, the other waiting thread will start boiling
the CPU...
P.S: Why didn't you go with the TMultiReadExclusiveWriteSynchronizer?
While it is an effective synchronization mechanism, I think it is kind of
"heavy-weighted" for my purpose - since I only need to protect just a few
reference counting instructions.
I roughly read TMREW's code and think it basically does the same thing,
but it make the thread go to sleep during read-write waiting.
Because the wait is expected to be very short, context switch is way
too expensive, I'd rather keep spining...
---
Adam Wu
_________________________________________________________________
与联机的朋友进行交流,请使用 Live Messenger;
http://get.live.com/messenger/overview
_______________________________________________
Delphi mailing list -> [email protected]
http://www.elists.org/mailman/listinfo/delphi