You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
10 KiB
416 lines
10 KiB
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
//
|
|
// SYNCHRO.CPP
|
|
//
|
|
// Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
|
|
//
|
|
#include "_synchro.h"
|
|
#include <ex\synchro.h>
|
|
|
|
// ========================================================================
|
|
//
|
|
// CLASS CMRWLock
|
|
//
|
|
// EnterRead()/LeaveRead() respectively lets a reader enter or leave
|
|
// the lock. If there is a writer or promotable reader in the lock,
|
|
// entry is delayed until the writer/promotable reader leaves.
|
|
//
|
|
// EnterWrite()/LeaveWrite() respectively lets a single writer enter
|
|
// or leave the lock. If there are any readers in the lock, entry
|
|
// is delayed until they leave. If there is another writer or a
|
|
// promotable reader in the lock, entry is delayed until it leaves.
|
|
//
|
|
// EnterPromote()/LeavePromote() respectively lets a single promotable
|
|
// reader enter or leave the lock. If there is a writer or another
|
|
// promotable reader in the lock, entry is delayed until the
|
|
// writer/promotable reader leaves. Otherwise entry is immediate,
|
|
// even if there are other (non-promotable) readers in the lock.
|
|
// Promote() promotes the promotable reader to a writer. If there
|
|
// are readers in the lock, promotion is delayed until they leave.
|
|
//
|
|
// Once a writer or promotable reader has entered the lock, it may
|
|
// reenter the lock as a reader, writer or promotable reader without
|
|
// delay. A reader cannot reenter the lock as a writer or promotable.
|
|
//
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::CMRWLock()
|
|
//
|
|
CMRWLock::CMRWLock() :
|
|
m_lcReaders(0),
|
|
m_dwWriteLockOwner(0),
|
|
m_dwPromoterRecursion(0)
|
|
{
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::FInitialize()
|
|
//
|
|
BOOL
|
|
CMRWLock::FInitialize()
|
|
{
|
|
return m_evtEnableReaders.FCreate( NULL, // default security
|
|
TRUE, // manual-reset
|
|
FALSE, // initially non-signalled
|
|
NULL ) // unnamed
|
|
|
|
&& m_evtEnableWriter.FCreate( NULL, // default security
|
|
FALSE, // auto-reset
|
|
FALSE, // initially non-signalled
|
|
NULL ); // unnamed
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::EnterRead()
|
|
//
|
|
void
|
|
CMRWLock::EnterRead()
|
|
{
|
|
(void) FAcquireReadLock(TRUE); // fBlock
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::FTryEnterRead()
|
|
//
|
|
BOOL
|
|
CMRWLock::FTryEnterRead()
|
|
{
|
|
return FAcquireReadLock(FALSE); // fBlock
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::FAcquireReadLock()
|
|
//
|
|
BOOL
|
|
CMRWLock::FAcquireReadLock(BOOL fAllowCallToBlock)
|
|
{
|
|
//
|
|
// Loop around trying to enter the lock until successful
|
|
//
|
|
for ( ;; )
|
|
{
|
|
//
|
|
// Poll the reader count/write lock
|
|
//
|
|
LONG lcReaders = m_lcReaders;
|
|
|
|
//
|
|
// If the write lock is held ...
|
|
//
|
|
if ( lcReaders & WRITE_LOCKED )
|
|
{
|
|
//
|
|
// ... check whether the writer is on this thread.
|
|
// If it is, then let this thread reenter the
|
|
// lock as a reader. Do not update the reader
|
|
// count in this case.
|
|
//
|
|
if ( m_dwWriteLockOwner == GetCurrentThreadId() )
|
|
break;
|
|
|
|
//
|
|
// If the writer is not on this thread, then wait
|
|
// until the writer leaves, then re-poll the
|
|
// reader count/write lock and try again.
|
|
//
|
|
// We only block if the caller allows us to block. If this is
|
|
// a FTryEnterRead call, we return FALSE right away.
|
|
//
|
|
if ( fAllowCallToBlock )
|
|
{
|
|
m_evtEnableReaders.Wait();
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Otherwise, the write lock was not held, so
|
|
// try to enter the lock as a reader. This only
|
|
// succeeds when no readers or writers enter or leave
|
|
// the lock between the time the reader count/
|
|
// write lock is polled above and now. If what is in
|
|
// the lock has changed, the whole operation is retried
|
|
// until the lock state doesn't change.
|
|
//
|
|
else
|
|
{
|
|
if ( lcReaders == /*reinterpret_cast<LONG>*/(
|
|
InterlockedCompareExchange(
|
|
(&m_lcReaders),
|
|
(lcReaders + 1),
|
|
(lcReaders))) )
|
|
#ifdef NEVER
|
|
reinterpret_cast<PVOID *>(&m_lcReaders),
|
|
reinterpret_cast<PVOID>(lcReaders + 1),
|
|
reinterpret_cast<PVOID>(lcReaders))) )
|
|
#endif // NEVER
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we made it to this point, we have acquired the read lock.
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::LeaveRead()
|
|
//
|
|
void
|
|
CMRWLock::LeaveRead()
|
|
{
|
|
//
|
|
// If the thread on which the reader is leaving also owns
|
|
// the write lock, then the reader leaving has no effect,
|
|
// as did entering.
|
|
//
|
|
if ( m_dwWriteLockOwner == GetCurrentThreadId() )
|
|
return;
|
|
|
|
//
|
|
// Otherwise, atomically decrement the reader count and
|
|
// check if a writer is waiting to enter the lock.
|
|
// If the reader count goes to 0 and a writer is waiting
|
|
// to enter the lock, then notify the writer that
|
|
// it is safe to enter.
|
|
//
|
|
if ( WRITE_LOCKED == InterlockedDecrement(&m_lcReaders) )
|
|
m_evtEnableWriter.Set();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::EnterWrite()
|
|
//
|
|
void
|
|
CMRWLock::EnterWrite()
|
|
{
|
|
//
|
|
// A writer is just a promotable reader that promotes immediately
|
|
//
|
|
EnterPromote();
|
|
Promote();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::FTryEnterWrite()
|
|
//
|
|
BOOL
|
|
CMRWLock::FTryEnterWrite()
|
|
{
|
|
BOOL fSuccess;
|
|
|
|
//
|
|
// Try to enter the lock as a promotable reader.
|
|
// Promote to a writer immediately if successful
|
|
// and return the status of the operation.
|
|
//
|
|
fSuccess = FTryEnterPromote();
|
|
|
|
if ( fSuccess )
|
|
Promote();
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::LeaveWrite()
|
|
//
|
|
void
|
|
CMRWLock::LeaveWrite()
|
|
{
|
|
LeavePromote();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::EnterPromote()
|
|
//
|
|
void
|
|
CMRWLock::EnterPromote()
|
|
{
|
|
//
|
|
// Grab the writer critical section to ensure that no other thread
|
|
// is already in the lock as a writer or promotable reader.
|
|
//
|
|
m_csWriter.Enter();
|
|
|
|
//
|
|
// Bump the promoter recursion count
|
|
//
|
|
++m_dwPromoterRecursion;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::FTryEnterPromote()
|
|
//
|
|
BOOL
|
|
CMRWLock::FTryEnterPromote()
|
|
{
|
|
BOOL fSuccess;
|
|
|
|
//
|
|
// Try to enter the writer critical section.
|
|
// Bump the recursion count if successful and
|
|
// return the status of the operation.
|
|
//
|
|
fSuccess = m_csWriter.FTryEnter();
|
|
|
|
if ( fSuccess )
|
|
++m_dwPromoterRecursion;
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::Promote()
|
|
//
|
|
void
|
|
CMRWLock::Promote()
|
|
{
|
|
//
|
|
// If the promotable reader has already been promoted
|
|
// then don't promote it again
|
|
//
|
|
if ( GetCurrentThreadId() == m_dwWriteLockOwner )
|
|
return;
|
|
|
|
//
|
|
// Assert that no other writer owns the lock.
|
|
//
|
|
Assert( 0 == m_dwWriteLockOwner );
|
|
Assert( !(m_lcReaders & WRITE_LOCKED) );
|
|
|
|
//
|
|
// Claim the lock
|
|
//
|
|
m_dwWriteLockOwner = GetCurrentThreadId();
|
|
|
|
//
|
|
// Stop readers from entering the lock
|
|
//
|
|
m_evtEnableReaders.Reset();
|
|
|
|
//
|
|
// If there are any readers in the lock
|
|
// then wait for them to leave. The InterlockedExchangeOr()
|
|
// is used to ensure that the test is atomic.
|
|
//
|
|
if ( InterlockedExchangeOr( &m_lcReaders, WRITE_LOCKED ) )
|
|
m_evtEnableWriter.Wait();
|
|
|
|
//
|
|
// Assert that the (promoted) writer is now the only thing in the lock
|
|
//
|
|
Assert( WRITE_LOCKED == m_lcReaders );
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// CMRWLock::LeavePromote()
|
|
//
|
|
void
|
|
CMRWLock::LeavePromote()
|
|
{
|
|
//
|
|
// No one should attempt to leave a promote block
|
|
// that they never entered.
|
|
//
|
|
Assert( m_dwPromoterRecursion > 0 );
|
|
|
|
//
|
|
// If the promotable reader promoted to a writer
|
|
// then start allowing readers back into the lock
|
|
// once the promoter recursion count reaches 0
|
|
//
|
|
if ( --m_dwPromoterRecursion == 0 &&
|
|
GetCurrentThreadId() == m_dwWriteLockOwner )
|
|
{
|
|
//
|
|
// Clear the write flag to allow new readers
|
|
// to start entering the lock.
|
|
//
|
|
m_lcReaders = 0;
|
|
|
|
//
|
|
// Unblock any threads with readers that are
|
|
// already waiting to enter the lock.
|
|
//
|
|
m_evtEnableReaders.Set();
|
|
|
|
//
|
|
// Release ownership of the write lock
|
|
//
|
|
m_dwWriteLockOwner = 0;
|
|
}
|
|
|
|
//
|
|
// Release the writer's/promoter's critical section reference.
|
|
// When this the last such reference is released, a new
|
|
// promoter/writer may enter the lock.
|
|
//
|
|
m_csWriter.Leave();
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// FREE FUNCTIONS
|
|
//
|
|
|
|
// ------------------------------------------------------------------------
|
|
//
|
|
// InterlockedExchangeOr()
|
|
//
|
|
// This function performs an atomic logical OR of a value to a variable.
|
|
// The function prevents more than one thread from using the same
|
|
// variable simultaneously. (Well, actually, it spins until it
|
|
// gets a consistent result, but who's counting...)
|
|
//
|
|
// Returns the value of the variable before the logical OR was performed
|
|
//
|
|
LONG InterlockedExchangeOr( LONG * plVariable, LONG lOrBits )
|
|
{
|
|
// The rather cryptic way this works is:
|
|
//
|
|
// Get the instantaneous value of the variable. Stuff it into a
|
|
// local variable so that it cannot be changed by another thread.
|
|
// Then try to replace the variable with this value OR'd together
|
|
// with the OR bits. But only replace if the variable's value
|
|
// is still the same as the local variable. If it isn't, then
|
|
// another thread must have changed the value between the two
|
|
// operations, so keep trying until they both succeed as if
|
|
// they had executed as one. Once the operation succeeds in
|
|
// changing the value atomically, return the previous value.
|
|
//
|
|
for ( ;; )
|
|
{
|
|
LONG lValue = *plVariable;
|
|
|
|
if ( lValue == /*reinterpret_cast<LONG>*/(
|
|
InterlockedCompareExchange(
|
|
(plVariable),
|
|
(lValue | lOrBits),
|
|
(lValue))) )
|
|
#ifdef NEVER
|
|
reinterpret_cast<PVOID *>(plVariable),
|
|
reinterpret_cast<PVOID>(lValue | lOrBits),
|
|
reinterpret_cast<PVOID>(lValue))) )
|
|
#endif // NEVER
|
|
return lValue;
|
|
}
|
|
}
|