|
|
 |

 |
|
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.
|
|
|
|
|
 |
|
|
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 exclusivein 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 againthat 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 revokedanother 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
Last Updated: Friday, July 6, 2001 |