Sample Chapter from Designing Solutions with COM+ Technologies by Ray Brown, Wade Baron, William D. Chadwick III
Training
Certifications
Books
Special Offers
Community




 
Designing Solutions with COM+ Technologies
Author Ray Brown, Wade Baron, William D. Chadwick III
Pages 928
Disk 1 Companion CD(s)
Level Intermediate
Published 12/19/2000
ISBN 9780735611276
ISBN-10 0-7356-1127-0
Price(USD) $69.99
To see this book's discounted price, select a reseller below.
 

More Information

About the Book
Table of Contents
Sample Chapter
Index
Related Series
Related Books
About the Author

Support: Book & CD

Rate this book
Barnes Noble Amazon Quantum Books

 


Chapter 4: Concurrency continued


Fine-Grained Locks

At times a coarse-grained, object-level lock can be too broad and simple to provide a sufficient amount of concurrency. The implementer of a concurrent object then might have to identify which code paths and object uses do not conflict with one another, and apply separate locking mechanisms to only the mutually exclusive areas. One way to accomplish this is to aggregate more than one lock and have the implementation acquire the lock instance that protects one particular data member or set of data members. Everything I showed you in the previous section is applicable to this approach.

An alternate and often complimentary fine-grained locking approach consists of pushing into the lock information about the nature of the data access sought and having the lock determine whether and how long to block a caller, depending on the types of access currently in progress. A lock that performs this task is sometimes called a group lock, because it services different groups of callers that are asking for and engaged in more than one type of activity.

The most common form of group lock is the multi-read, single-write (MRSW) lock, also called the read/write lock. This lock distinguishes between read and update access to a resource (usually data in memory), offering two distinct lock acquisition methods. This lock can have an arbitrarily large number of lock owners in its reader group but only one owner in its writer group. In addition, the two groups are mutually exclusive—in other words, no readers can own the lock if a writer currently owns it, and vice versa.

MRSW locks are so popular because in-memory data is amenable to simultaneous reading but naturally susceptible to concurrency when updates are in progress. We have already seen why simultaneous updates cause trouble. The reason even read access must be blocked while a single update operation is in progress concerns the internal consistency of multiple data elements, when the update involves more than one memory location. Common data structures such as trees and lists have update isolation requirements similar to the ones that database transactions offer. For example, to remove a node from something as simple as a doubly linked list, more than one pointer must be adjusted. Traversing the list while one pointer has been adjusted and the other has not could easily lead to faults. Therefore, readers must be blocked until the entire update operation has been completed. This is also why it is often necessary to protect access to more than one data element by a single lock: the data elements might be related, and you might have to avoid an inconsistent state.

Library vendors commonly certify their products as safe for concurrent access as long as update operations are not involved. In fact, the STL portion of the Standard C++ Library does not contain locks to protect itself from concurrent read/write access to the same data structures, and if multiple threads can access artifacts such as container objects, such access must be guarded by at least an MRSW lock. If you find that course-grained locking provides insufficient concurrency in an object, an MRSW lock might help solve the problem. We will spend the rest of this section examining MRSW lock features and implementations.

MRSW Lock Concepts 

Its description makes the MRSW lock appear deceptively simple. Upon some consideration, however, you will discover a number of subtleties in the MRSW specification whose resolution affects how an MRSW lock instance can be used. Let’s examine these possible features:

  • Recursion. Win32 mutexes and critical sections are recursive. For an MRSW lock, recursiveness is defined by a reader’s ability to reacquire read access and a writer’s ability to reacquire write access while still holding the lock, all without blocking.
  • Promotion. This feature is defined by a reader’s ability to acquire a write lock without releasing its read lock.
  • Inversion. This feature is defined by a writer’s ability to acquire a read lock without releasing its write lock. You might ask why you ever would want to do this, given that a write lock is really a superset of a read lock. But the need for inversion frequently arises in implementations, when functions holding a write lock call functions requiring only a read lock. Releasing the write lock before calling the other function is often not an option, since it would break up the atomicity of the update operation.
  • Writer starvation. A steady stream of overlapping read lock requests might prevent blocked writers from ever proceeding into the lock. A sophisticated lock implementation will block readers when writers are queued, even if the active reader group is not empty.

MRSW Lock Implementations 

Windows NT and Windows 2000 offer an MRWS lock implementation to kernel-mode device driver implementers. Unfortunately, the only such lock available to Win32 applications is not documented.11 In addition, this lock does not offer promotion (an attempt to promote will result in deadlock) and does not prevent writer starvation.

Because the lock functions remain undocumented in Windows 2000, and because it is quite possible to build your own MRSW lock on top of Win32 synchronization objects, I do not advocate using the undocumented system MRSW lock. Building an MRSW lock on top of the Win32 API also makes it portable to all Win32 platforms, not just the current versions of Windows NT and Windows 2000.

A Full-Featured Win32 MRSW Lock 

Building your own MRSW lock is possible, but it is not easy. In fact, implementers face a number of subtle and sometimes rare race conditions that must be avoided. Implementing promotion is particularly challenging, since any reader can be promoted to a writer only when all readers are applying to be upgraded to writers. Leaving MRSW implementation as an exercise for you to do on your own would be a bit unfair, so I will dedicate the rest of this section to one complete MRSW lock implementation.

The lock whose source code follows has these operational characteristics:

  • An arbitrary number of threads can simultaneously hold a read lock.
  • Only one thread at a time can hold a write lock.
  • If a write lock has been acquired, attempts to acquire a read lock will block (or time out) until the write lock has been released, unless the request to obtain the read lock is being made by the single thread (which holds the write lock), in which case the request will succeed immediately.
  • If one or more read locks have been acquired, attempts to acquire a write lock will block (or time out) until all read locks have been released, unless all read locks are being held by threads that request to obtain a write lock, in which case one thread (but not necessarily one holding a read lock) will be granted the write lock. All other threads will remain blocked until this condition evaluates to true again—that is, all threads holding read locks are blocked on write lock requests and no outstanding write lock exists.
  • If a write lock has been acquired, only the single thread holding the lock can reacquire it; all other threads will block (or time out) on a write lock request until the write lock has been released.
  • The lock can be configured to accept time-out values in lock acquisition requests. If so configured, the lock has these additional characteristics:
    • The lock uses slightly more resources and is slightly slower.
    • A time-out value can be passed to the acquisition methods; this value is passed through to Win32 wait functions.
    • The acquisition methods return false on failures because of time-outs.
    • The ResetStoredTimeout method or the constructor can be used to store a default time-out in the lock object; this value will be used if the MRSW_STORED_TIMEOUT constant is passed to the acquisition methods.
    • When a thread that holds a read lock requests a write lock, the time-out parameter to the acquisition method is automatically overridden to carry the value INFINITE.
    • While a write lock is being requested (and the requester is blocked), and while a write lock is outstanding, all new threads will block (or time out) on any lock request. The order in which these requests will be granted is guaranteed first in, first out (FIFO) when the lock is configured to accept time-out values; otherwise, the system’s policy for critical sections applies.

Being unable to honor requests for time-out during promotion is an interesting, if unexpected, byproduct of MRSW requirements: at the time a reader submits its request for promotion, a blocked writer can be admitted into the lock if that reader was to the last active reader not stalled in a promotion request. If we permitted this promotion request (or that of any other reader stalled in its promotion request at the time a writer is released) to time out, the caller would be informed that the promotion time interval has indeed expired. However, that caller would certainly still expect to hold the read lock it was attempting to upgrade. Continuing with its operation after the promotion failure could lead to concurrent read and write operations, which cannot be allowed. All promotions therefore are executed without a time-out.

Let’s look at the source code for this MRSW lock:

// MRSWLock.h : header file
//

#ifndef MRSWLock_H
#define MRSWLock_H

#include "AtlSupport.h"

#include "ComSTLBridgeExpImp.h"

///////////////////////////////////////////////////////////////////////////
// CMRSWLockRoot: the multi-reader, single-writer lock; the lock can be
//                configured to accept a time-out value on acquisition

class DLLEXPORTIMPORT CMRSWLockRoot
{
// Types and constants
public:
    // Constant indicates that previously set time-out should be used
    static const DWORD MRSW_STORED_TIMEOUT;

// Constructors - destructors
    CMRSWLockRoot(bool bWaitableLock = false, DWORD nInitialTimeout =
                  INFINITE);
    ~CMRSWLockRoot();

    // Copy and assignment
private:
    CMRSWLockRoot(const CMRSWLockRoot&);                 // not impl
    CMRSWLockRoot& operator = (const CMRSWLockRoot&);    // not impl

// Operations
public:
    bool AcquireReadLock(DWORD nMillisecondTimeout = MRSW_STORED_TIMEOUT);
    bool AcquireWriteLock(DWORD nMillisecondTimeout = MRSW_STORED_TIMEOUT);
    void ReleaseReadLock();
    void ReleaseWriteLock();

    void ResetStoredTimeout(DWORD nMilliseconds);

// Implementation
private:
    class CPrivateLockRouter
    {
    public:
        inline CPrivateLockRouter(CMRSWLockRoot& rcTarget);

        inline void Lock();
        inline void Unlock();

    private:
        CMRSWLockRoot& m_rcTarget;
    };

    friend class CPrivateLockRouter;

    class CPrivateLock : public CCSBLock<CPrivateLockRouter>
    {
    public:
        inline CPrivateLock(CMRSWLockRoot& rcTarget);

    private:
        CPrivateLockRouter m_cLockRouter;
    };

    typedef std::map<DWORD, unsigned short> t_ARM;

    inline void            Lock();
    inline void            Unlock();
    bool                   AcquireBlade(DWORD nMillisecondTimeout);
    inline void            ReleaseBlade();
    void                   UpdateEvent(bool bTowardsSignalledState);

    const bool             m_bWaitableLock;
    DWORD                  m_nStoredTimeout;
    CRITICAL_SECTION       m_tInternalLock;

    union
    {
        CRITICAL_SECTION   m_tReadersWriterBlade;
        HANDLE             m_hReadersWriterBlade;
    };

    HANDLE                 m_hSingleWriterStop;
    t_ARM                  m_cActiveReaders;
};

/////////////////////////////////////////////////////////////////////////////
// The read and write lock classes in the derivation diamond are provided
// for the benefit of the auto-balance lock template

class DLLEXPORTIMPORT CMRSWReadLock : public virtual CMRSWLockRoot
{
public:
    void Lock();
    void Unlock();
};

class DLLEXPORTIMPORT CMRSWWriteLock : public virtual CMRSWLockRoot
{
public:
    void Lock();
    void Unlock();
};

class DLLEXPORTIMPORT CMRSWLock : public CMRSWReadLock,
                                  public CMRSWWriteLock
{
// Types
public:
    typedef CMRSWReadLock t_ReadLock;
    typedef CMRSWWriteLock t_WriteLock;

// Construction
    CMRSWLock(bool bWaitableLock = false, DWORD nInitialTimeout =
              INFINITE);
};

///////////////////////////////////////////////////////////////////////////
// TMRSWLock: a threading model–sensitive template that resolves to either
//            the CMRSWLock class or a fake lock

// The SectionSwitch helps TMRSWLock in branching to full or stubbed
// functionality by examining the underlying critical section class; it can
// be used directly if desired
template <class TCriticalSection>
class TMRSWSectionSwitch
{
// Types and constants - for compatibility with CMRSWLock
public:
    typedef TMRSWSectionSwitch t_ReadLock;
    typedef TMRSWSectionSwitch t_WriteLock;
    enum { MRSW_STORED_TIMEOUT };

// Constructors - destructors
    TMRSWSectionSwitch(bool /*bWaitableLock*/ = false,
                       DWORD /*nInitialTimeout*/ = INFINITE) {}

   // Copy and assignment
private:
    TMRSWSectionSwitch(const TMRSWSectionSwitch&);              // not impl
    TMRSWSectionSwitch& operator = (const TMRSWSectionSwitch&); // not impl

// Operations
public:
    bool AcquireReadLock(DWORD /*nMillisecondTimeout*/ =
                         MRSW_STORED_TIMEOUT)
        { return true; }
    bool AcquireWriteLock(DWORD /*nMillisecondTimeout*/ =
                          MRSW_STORED_TIMEOUT)
        { return true; }
    void ReleaseReadLock() {}
    void ReleaseWriteLock() {}
    void ResetStoredTimeout(DWORD /*nMilliseconds*/) {}
    void Lock() {}
    void Unlock() {}
};

template <>
class TMRSWSectionSwitch<CComCriticalSection> : public CMRSWLock
{
public:
    TMRSWSectionSwitch(bool bWaitableLock = false,
                       DWORD nInitialTimeout = INFINITE) :
        CMRSWLock (bWaitableLock, nInitialTimeout) {}
};

// The TMRSWLock will be the MRSW lock type most useful to the majority of
// clients, since it is sensitive to the ATL threading model type
template <class TThreadModel>
class TMRSWLock : public TMRSWSectionSwitch<TThreadModel::CriticalSection>
{
public:
    TMRSWLock(bool bWaitableLock = false, DWORD nInitialTimeout =
              INFINITE) :
        TMRSWSectionSwitch<TThreadModel::CriticalSection>
            (bWaitableLock, nInitialTimeout) {}
};

///////////////////////////////////////////////////////////////////////////
#endif

// MRSWLock.cpp : implementation file
//

#include "stdafx.h"
#include "MRSWLock.h"
#include "CSBError.h"
#include "ComSTLBridgeStatusCodes.h"

///////////////////////////////////////////////////////////////////////////
// CMRSWLockRoot

///////////////////////////////////////////////////////////////////////////
// Class variable initializations

const DWORD CMRSWLockRoot::MRSW_STORED_TIMEOUT = INFINITE - 1;

///////////////////////////////////////////////////////////////////////////
// Construction and destruction

CMRSWLockRoot::CMRSWLockRoot(bool bWaitableLock /*= false*/,
                             DWORD nInitialTimeout /*= INFINITE*/) :
    m_bWaitableLock (bWaitableLock),
    m_nStoredTimeout (nInitialTimeout)
{
    _ASSERTE (bWaitableLock || nInitialTimeout == INFINITE);

    // Initialize internally used lock; may throw exception
    InitializeCriticalSection(&m_tInternalLock);

    bool bInitStage2 = false;
    try
    {
        // Initialize blade; blade type determined by flag
        if (bWaitableLock)
        {
            if (! (m_hReadersWriterBlade = CreateMutex(NULL, FALSE, NULL)))
                CCSBStdException::CSBThrowException(static_cast <HRESULT>
                    (HRESULT_FROM_WIN32(GetLastError())));
        }
        else
            InitializeCriticalSection(&m_tReadersWriterBlade);

        bInitStage2 = true;

        // Initialize event
        if (! (m_hSingleWriterStop = CreateEvent(NULL, TRUE, TRUE, NULL)))
            CCSBStdException::CSBThrowException(static_cast <HRESULT>
                (HRESULT_FROM_WIN32(GetLastError())));
    }
    catch (…)
    {
        // Free resources allocated before the failure occurred
        DeleteCriticalSection(&m_tInternalLock);

        if (bInitStage2)
            if (bWaitableLock)
                _VERIFYE (CloseHandle(m_hReadersWriterBlade));
            else
                DeleteCriticalSection(&m_tReadersWriterBlade);

        throw;
    }
}

CMRSWLockRoot::~CMRSWLockRoot()
{
    // Lock must not be in use now
    _ASSERTE (m_cActiveReaders.empty());

    // Free all resources used by this lock
    DeleteCriticalSection(&m_tInternalLock);

    if (m_bWaitableLock)
        _VERIFYE (CloseHandle(m_hReadersWriterBlade));
    else
        DeleteCriticalSection(&m_tReadersWriterBlade);

    _VERIFYE (CloseHandle(m_hSingleWriterStop));
}

///////////////////////////////////////////////////////////////////////////
// Operations

bool CMRSWLockRoot::AcquireReadLock(DWORD nMillisecondTimeout
                                    /*= MRSW_STORED_TIMEOUT*/)
{
    // Determine this thread’s current read lock count
    const DWORD nThreadId = GetCurrentThreadId();
    CPrivateLock cLock(*this);  // lock for map access

    t_ARM::iterator cCurThreadPos = m_cActiveReaders.find(nThreadId);

    if (cCurThreadPos != m_cActiveReaders.end())
    {
        // Reader lock is being reacquired by this thread;
        // bump acquisition count and return without further ado
        ++(*cCurThreadPos).second;
        return true;
    }

    // This is an initial read lock acquisition for this thread; we must
    // acquire the blade so that all writers remain excluded after blade
    // acquisition and until the read lock is released
    cLock.Unlock();

    if (! AcquireBlade(nMillisecondTimeout))
        return false;

    // The thread is getting ready to run with lock; enter its ID into the
    // active reader map (and do *not* release the blade before this entry
    // has been made; otherwise, a writer could block on the writer stop
    // event forever)
    cLock.Lock();  // lock for map access

    try
    {
        m_cActiveReaders[nThreadId] = 1;
    }
    catch (…)
    {
        ReleaseBlade();
        throw;
    }

    cLock.Unlock();  // map access complete

    // While holding the blade, prevent writers from achieving lock by
    // resetting the writer stop event
    _VERIFYE (ResetEvent(m_hSingleWriterStop));

    // Release blade now for acquisition by other new readers or writers
    ReleaseBlade();

    return true;
}

/*
   Note: the time-out value will be converted to INFINITE automatically for
         any calling thread that already holds a read lock
*/
bool CMRSWLockRoot::AcquireWriteLock(DWORD nMillisecondTimeout
                                     /*= MRSW_STORED_TIMEOUT*/)
{
    // Determine whether this is a read-to-write conversion
    const DWORD nThreadId = GetCurrentThreadId();
    CPrivateLock cLock(*this);  // lock for map/set access and container
                                // crunch

    t_ARM::iterator cCurThreadPos = m_cActiveReaders.find(nThreadId);
    const bool bLockConvert = cCurThreadPos != m_cActiveReaders.end();

    // We are about to become a waiting writer; set the recursion count in
    //  the map to zero, thus indicating that this reader thread is waiting
    //  to achieve write lock
    unsigned short nSavedRecursionCount;

    if (bLockConvert)
    {
        // Note: we do not simply remove the entry from the map in order to
        // avoid the (rather complicated) issue of not being able to
        // reacquire memory for it before returning
        nSavedRecursionCount = (*cCurThreadPos).second;
        (*cCurThreadPos).second = 0;

        // The preceding operation might have converted all active readers 
        // to waiting writers, and thus the writer stop event might have to 
        // be opened for this or another currently blocked writer thread
        UpdateEvent(true);
    }

    // Adjust/retrieve time-out value
    if (m_bWaitableLock)
        if (bLockConvert)
            nMillisecondTimeout = INFINITE;
        else if (nMillisecondTimeout == MRSW_STORED_TIMEOUT)
            nMillisecondTimeout = m_nStoredTimeout;

    cLock.Unlock();

    // Now attempt to acquire the blade
    bool bAcquisitionException;

    do
    {
        try
        {
            if (! AcquireBlade(nMillisecondTimeout))
            {
                _ASSERTE (! bLockConvert);
                return false;
            }

            bAcquisitionException = false;
        }
        catch (const CCSBStdException& rcException)
        {
            _UNUSED (rcException);

            if (! bLockConvert)
                throw;

            bAcquisitionException = true;

            // A converting thread cannot be allowed to escape at this
            // stage of the lock acquisition process, since the above
            // ContainerCrunch might have released another writer thread;
            // thus, returning now could lead to simultaneous read/write
            // activity
            ATLTRACE(_T("MRSW read to write lock conversion failed "
                        "unexpectedly at mutex with error %lx; "
                        "retrying...\n"), rcException.ErrorCode());
        }
    } while (bAcquisitionException);

    // Now wait for the stop event to become signaled; this will occur (if
    // it has not already) when all currently active readers have released
    // their locks or attempt to convert to write locks
    DWORD nWaitResult;

    while (true)
    {
        nWaitResult = WaitForSingleObject(m_hSingleWriterStop,
                                          nMillisecondTimeout);

        _ASSERTE (nWaitResult != WAIT_TIMEOUT || ! bLockConvert);

        if (bLockConvert && nWaitResult == WAIT_FAILED)
        {
            ATLTRACE(_T("MRSW read to write lock conversion failed "
                        "unexpectedly at event with error %lx; "
                        "retrying...\n"), GetLastError());
            continue;
        }

        if (nWaitResult == WAIT_OBJECT_0)
            break;

        // We have been unsuccessful at acquiring the event and give up
        ReleaseBlade();

        if (nWaitResult == WAIT_TIMEOUT)
            return false;

        _ASSERTE (nWaitResult == WAIT_FAILED);

        CCSBStdException::CSBThrowException(static_cast <HRESULT>
            (HRESULT_FROM_WIN32(GetLastError())));
    }

    // We have successfully passed the event and are now the only active
    // thread holding any lock on this object while holding the blade

    // Clean up before returning
    if (bLockConvert)
    {
        (*cCurThreadPos).second = nSavedRecursionCount;
        UpdateEvent(false);
    }

    return true;
}

void CMRSWLockRoot::ReleaseReadLock()
{
    // Locate thread entry in map
    CPrivateLock cLock(*this);  // lock for map access and container crunch

    t_ARM::iterator cCurThreadPos =
        m_cActiveReaders.find(GetCurrentThreadId());
    _ASSERTE (cCurThreadPos != m_cActiveReaders.end());

    // If this is a recursive lock, decrement count and get out
    if ((*cCurThreadPos).second > 1)
    {
        --(*cCurThreadPos).second;
        return;
    }

    // Otherwise, this thread’s entry must be removed from the active 
    // reader map and the writer release condition must be evaluated
    m_cActiveReaders.erase(cCurThreadPos);
    UpdateEvent(true);
}

void CMRSWLockRoot::ReleaseWriteLock()
{
    // Releasing the blade will allow queued writers or readers to achieve
    // lock
    ReleaseBlade();
}

void CMRSWLockRoot::ResetStoredTimeout(DWORD nMilliseconds)
{
    CPrivateLock cLock(*this);
    m_nStoredTimeout = nMilliseconds;
}

///////////////////////////////////////////////////////////////////////////
// Implementation

/*
   Acquire internal short-term lock. Not in header because of private
   protection status.
*/
void CMRSWLockRoot::Lock()
{
    EnterCriticalSection(&m_tInternalLock);
}

/*
   Release internal short-term lock. Not in header because of private
   protection status.
*/
void CMRSWLockRoot::Unlock()
{
    LeaveCriticalSection(&m_tInternalLock);
}

bool CMRSWLockRoot::AcquireBlade(DWORD nMillisecondTimeout)
{
    _ASSERTE (m_bWaitableLock || nMillisecondTimeout ==
              MRSW_STORED_TIMEOUT);

    if (! m_bWaitableLock)
    {
        EnterCriticalSection(&m_tReadersWriterBlade);
        return true;
    }

    // Find time-out value to use
    if (m_bWaitableLock && nMillisecondTimeout == MRSW_STORED_TIMEOUT)
    {
        CPrivateLock cLock(*this);  // lock for non-const obj state access
        nMillisecondTimeout = m_nStoredTimeout;
    }

    const DWORD nWaitResult = WaitForSingleObject(m_hReadersWriterBlade,
                                                  nMillisecondTimeout);

    if (nWaitResult == WAIT_FAILED)
        CCSBStdException::CSBThrowException(static_cast <HRESULT>
            (HRESULT_FROM_WIN32(GetLastError())));

    if (nWaitResult == WAIT_TIMEOUT)
        return false;

    if (nWaitResult == WAIT_ABANDONED)
        ATLTRACE(_T("MRSWLock blade acquired through abandoned " 
                    "mutex.\n"));

    return true;
}

void CMRSWLockRoot::ReleaseBlade()
{
    if (m_bWaitableLock)
        _VERIFYE (ReleaseMutex(m_hReadersWriterBlade));
    else
        LeaveCriticalSection(&m_tReadersWriterBlade);
}

/*
   The method compares the currently active readers against the set of
   writers now waiting to run. If every active reader is also a writer
   waiting to run and the given flag instructs to look for this condition,
   we signal the writer stop event in order to allow that single writer
   (hence the event’s name) that made it to the event to proceed now. If
   the condition is false and we were instructed to look for falsehood, we
   reset the event.
   Note: the internal lock must have been acquired before calling this
         method. This is natural, since it is usually called after an
         adjustment to the reader map, for which a lock must have been
         acquired already.
*/
void CMRSWLockRoot::UpdateEvent(bool bTowardsSignalledState)
{
    for (t_ARM::iterator cArmIter = m_cActiveReaders.begin();
        cArmIter != m_cActiveReaders.end(); ++cArmIter)

        if ((*cArmIter).second)
        {
            // A currently executing active reader exists
            if (! bTowardsSignalledState)
                _VERIFYE (ResetEvent(m_hSingleWriterStop));

            return;
        }

    // There are no active readers, or all active readers are waiting to
    // acquire a write lock
    if (bTowardsSignalledState)
        _VERIFYE (SetEvent(m_hSingleWriterStop));
}

///////////////////////////////////////////////////////////////////////////
// CMRSWLockRoot::CPrivateLock and CPrivateLockRouter
// These private classes prevent non-CMRSWLock code from gaining access to
// the private Lock and Unlock methods while providing auto-balanced access
// for CMRSWLock code to these methods

CMRSWLockRoot::CPrivateLockRouter::
CPrivateLockRouter(CMRSWLockRoot& rcTarget) :
    m_rcTarget (rcTarget)
{
}

void CMRSWLockRoot::CPrivateLockRouter::Lock()
{
    m_rcTarget.Lock();
}

void CMRSWLockRoot::CPrivateLockRouter::Unlock()
{
    m_rcTarget.Unlock();
}

CMRSWLockRoot::CPrivateLock::CPrivateLock(CMRSWLockRoot& rcTarget) :
    CCSBLock<CPrivateLockRouter> (m_cLockRouter, false),
    m_cLockRouter (rcTarget)
{
    // Now that lock router is properly initialized, have CCSBLock clamp
    // down on it
    Lock();
}

///////////////////////////////////////////////////////////////////////////
// CMRSWLock, CMRSWReadLock, CMRSWWriteLock: these classes form the bottom
// of the inheritance diamond and allow for the implicit conversion of a
// compound lock to a function-specific lock offering one simple Lock and
// Unlock method each. These intermediate types are therefore suitable for
// use with auto-balance lock templates.

void CMRSWReadLock::Lock()
{
    if (! AcquireReadLock())
        CCSBStdException::
            CSBThrowException(CSBSUPPORTLIB_E_MRSWLOCK_TIMEOUT);
}

void CMRSWReadLock::Unlock()
{
    ReleaseReadLock();
}

void CMRSWWriteLock::Lock()
{
    if (! AcquireWriteLock())
        CCSBStdException::
            CSBThrowException(CSBSUPPORTLIB_E_MRSWLOCK_TIMEOUT);
}

void CMRSWWriteLock::Unlock()
{
    ReleaseWriteLock();
}

CMRSWLock::CMRSWLock(bool bWaitableLock /*= false*/,
                     DWORD nInitialTimeout /*= INFINITE*/) :
    CMRSWLockRoot (bWaitableLock, nInitialTimeout)
{
}
The diamond inheritance hierarchy from CMRSWLock via CMRSWReadLock and from CMRSWWriteLock to CMRSWLockRoot is intended to allow for easy use of a CMRSWLock instance with the simple lock template, without the use of an adapter. You therefore can acquire read and write locks as follows:

CMRSWLock m_cMRSWLock;
CCSBLock<CMRSWReadLock> cReadLock(m_cMRSWLock);
CCSBLock<CMRSWWriteLock> cWriteLock(m_cMRSWLock);  // promotion
The template TMRSWLock is provided for use in ATL object templates. Type instantiation with, for example, CComMultiThreadModel, results in a true MRSW lock. Instantiation with ATL thread model types that provide only fake critical sections yields a fake MRSW lock.

Since this lock keeps track of physical thread IDs instead of causality IDs, it cannot handle causalities that reenter with different threads. That is one functionality upgrade that I will leave to you.


1 To gain an understanding of COM’s early history and purpose, I recommend that you read Chapter 1 of Don Box’s excellent work, Essential COM (Addison-Wesley, 1998). Don explores the meaning and mechanism of binary compatibility in a way that will help you understand the soul of COM.

2 If you find yourself wanting to implement one, have a look at Keith Brown’s Universal Delegator first (http://www.develop.com/kbrown/com/samples.htm). This is an excellent generic interceptor demonstrating competent resolutions of all problems discussed here (for the x86 platform). And you might be able to reuse it instead of implementing your own.

3 The other threading model setting a Visual Basic 6 ActiveX DLL project supports is Apartment Threaded. The Visual Basic run-time environment solves global data access synchronization issues by giving each apartment a separate copy of all global data in the project. The debugging environment, however, does not support this and runs the project as though Single Threaded had been chosen as the threading model used while debugging the DLL.

4 However, the single, concurrent thread-per-domain guarantee is weakened in the case of domains that span hosts. This is understandable, since an efficient network-wide synchronization object does not exist. Attempting to simulate such a lock undoubtedly would have a deleterious effect on performance.

5 The writers of the COM+ portion of the Platform SDK documentation appear as confused about this as everyone else. The documentation incorrectly states, "Classes that are ThreadingModel=Apartment require either Synchronization=Required or Synchronization=RequiresNew."

6 The Platform SDK documentation also refers to such objects as "agile."

7 When an interface pointer is imported into a context, the resulting proxy points directly to the context in which the target object resides, as shown earlier. But importation has not taken place yet when making a GIT registration. Therefore, the registering context must remain alive until the registration is revoked—another heavy burden on the FTM programming model.

8 Note, however, that doing so means that they must not be activated by configured components in-context.   

9 In the case of an increment or decrement of a variable of type LONG, the Win32 functions InterlockedIncrement and InterlockedDecrement can be more efficient than using a synchronization object. These functions can use an atomic machine increment or decrement instruction if available on a processor architecture.

10 Difficult, but not impossible. You can make a call to CoInitialize, followed immediately by a call to CoUninitialize. If the return value from CoInitialize is RPC_E_CHANGED_MODE, the calling thread is an MTA thread.

11 See the article, "NT’s Undocumented Multi-Reader/Single-Writer Lock," by Jeff Claar, Windows Developer’s Journal, January 1999.


Previous  |   Top of  Sample Chapter   |   Next




Top of Page


Last Updated: Friday, July 6, 2001