windows-nt/Source/XPSP1/NT/inetsrv/iis/inc/readmost.hxx
2020-09-26 16:20:57 +08:00

223 lines
6.6 KiB
C++

/*++
Copyright (c) 1998-2000 Microsoft Corporation
Module Name :
readmost.hxx
Abstract:
Read-mostly Data Cache
Author:
George V. Reilly (GeorgeRe) 14-Sep-1998
(from an idea by Neel Jain)
Environment:
Win32 - User Mode
Project:
Internet Information Server RunTime Library
Revision History:
--*/
#ifndef __READMOST_HXX__
#define __READMOST_HXX__
//=====================================================================
// Locks are expensive and they kill concurrency on multiprocessor
// systems. CDataCache<_T> is a lock-free cache that is suitable for
// "read-mostly" data structures; i.e., data structures that are hardly
// ever updated. We use a monotonically increasing sequence number to
// version stamp the data in the cache. Whenever the data is altered
// (which can only happen through the Write() method), the version number
// is updated. For a Read(), if the version number is the same both
// before and after the data itself is copied into an out parameter, then
// the Read() obtained a valid copy of the data.
//=====================================================================
// Use a portable implementation with interlocked routines that doesn't
// rely on processor-specific memory barrier intrinsics?
#undef READMOST_INTERLOCKED
#ifndef READMOST_INTERLOCKED
#if defined(_M_IA64)
extern "C" void __mf(void);
#pragma intrinsic(__mf)
#endif // _M_IA64
#endif // !READMOST_INTERLOCKED
#if !defined( dllexp)
#define dllexp __declspec( dllexport)
#endif // !defined( dllexp)
template <class _T>
class dllexp CDataCache
{
protected:
// Place the cached data first to preserve its alignment constraints.
volatile _T m_tData;
// Mark the sequence number (version stamp) as volatile to ensure that
// the compiler doesn't cache its value in a register. Mark it as mutable
// so that we can use the Interlocked operations on the sequence
// number in const member functions.
mutable volatile LONG m_nSequence;
enum {
UPDATING = 0xffffffff, // out-of-band odd value => cache is invalid
INITIAL = UPDATING + 1, // even value
STEP = 2, // ensures m_nSequence will never == UPDATING
BOGUS = UPDATING + STEP,// impossible value, never used
};
#ifdef READMOST_INTERLOCKED
LONG
_ReadSequence() const
{
// Since m_nSequence will never be equal to BOGUS, this
// will atomically read the value of m_nSequence, but not
// modify it. On architectures that need such things, it
// will have the side effect of erecting a read memory
// barrier both before and after reading the value of m_nSequence.
return InterlockedCompareExchange((LONG*) &m_nSequence, BOGUS, BOGUS);
}
#else // !READMOST_INTERLOCKED
// On some systems, such as Alphas and Itaniums, the compiler or
// processor can issue out-of-order (speculative) reads and writes.
// _ReadMemoryBarrier() and _WriteMemoryBarrier() force serialization
// of memory accesses.
static void
_ReadMemoryBarrier()
{
#if defined(_M_IA64)
__mf();
#endif // _M_IA64
}
// Read the value of m_nSequence, imposing memory barriers
// both before and after reading m_nSequence.
LONG
_ReadSequence() const
{
_ReadMemoryBarrier();
const LONG nSequence = m_nSequence;
_ReadMemoryBarrier();
return nSequence;
}
// Not currently used, as we rely on InterlockedExchange in
// _SetSequence to do the right thing with write memory barriers.
static void
_WriteMemoryBarrier()
{
#if defined(_M_IA64)
__mf();
#endif // _M_IA64
}
#endif // !READMOST_INTERLOCKED
// Update m_nSequence, returning its old value. InterlockedExchange
// has the side effect of erecting a write memory barrier both
// before and after updating m_nSequence.
LONG
_SetSequence(
LONG nNewValue)
{
return InterlockedExchange((LONG*) &m_nSequence, nNewValue);
}
public:
// Default ctor. Rely on _T::_T() to do something useful.
CDataCache()
: m_nSequence(INITIAL)
{}
// Ctor.
CDataCache(const _T& t)
: m_tData(t), m_nSequence(INITIAL)
{}
// Read the contents of the cache into rtOut. Returns `true' if
// successful, `false' otherwise (in which case rtOut is garbage).
// You should retry if Read() returns `false'.
bool
Read(
_T& rtOut) const
{
const LONG nSequence1 = _ReadSequence();
// Is the data being updated on another thread?
if (nSequence1 != UPDATING)
{
// No, so read the data into rtOut.
// The weird const_cast syntax is necessitated by the volatile
// attribute on m_tData.
rtOut = * const_cast<_T*>(&m_tData);
// If the sequence number is unchanged, the read was valid.
const LONG nSequence2 = _ReadSequence();
return (nSequence1 == nSequence2);
}
// Another thread was updating the cache, so Read failed.
// The caller should probably retry.
return false;
}
// Updates the contents of the cache. Returns `true' if the cache was
// successfully updated, `false' otherwise (because the cache is already
// being updated on some other thread).
bool
Write(
const _T& rtIn)
{
// Atomically set m_nSequence to UPDATING.
const LONG nSequence = _SetSequence(UPDATING);
// If the old value of m_nSequence was not UPDATING,
// then we now "own" the cache.
if (nSequence != UPDATING)
{
// Update the cached data. The weird const_cast syntax is
// necessitated by the volatile attribute on m_tData.
* const_cast<_T*>(&m_tData) = rtIn;
// Finally, update the sequence number. The implicit
// memory barriers in InterlockedExchange will force
// the write of m_tData to complete before m_nSequence
// acquires its new value, and will force the write
// of m_nSequence to complete before Write() returns.
_SetSequence(nSequence + STEP);
return true;
}
// Another thread already owned the cache, so Write failed.
// This is probably fine, but that determination must be
// made by the routine that called Write(), since it
// understands the semantics of its caching and Write() doesn't.
return false;
}
};
#endif // __READMOST_HXX__