374 lines
10 KiB
C
374 lines
10 KiB
C
|
//
|
||
|
// LOCKQ.H
|
||
|
//
|
||
|
// This file contains classes which define a queue mechanism which will
|
||
|
// safely synchronize additions and removals from the queue, where every
|
||
|
// thread which appends to the Queue must be prepared to deal with owning the
|
||
|
// queue. Additionally, elements will come off the queue in the same order
|
||
|
// that they are appended.
|
||
|
//
|
||
|
// The structure of a thread using this stuff should be the following :
|
||
|
//
|
||
|
// class CUsefull : public CQElement { } ;
|
||
|
// template CLockQueue< CUsefull > UsefullQ ;
|
||
|
//
|
||
|
//
|
||
|
// if( UsefullQ.Append( pUsefull ) ) {
|
||
|
//
|
||
|
// while( UsefullQ.GetHead( &pUsefullWorkItem ) ) {
|
||
|
// /* Do some usefull work. */
|
||
|
//
|
||
|
// UsefullQ.Remove() ;
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// Implementation Schedule for all classes defined in this file :
|
||
|
// 1 day
|
||
|
//
|
||
|
// Unit Test schedule for all classes defined in this file :
|
||
|
// 1 day
|
||
|
// Unit Testing should consist of a multi theaded appli
|
||
|
//
|
||
|
//
|
||
|
|
||
|
|
||
|
|
||
|
#ifndef _LOCKQ_H_
|
||
|
#define _LOCKQ_H_
|
||
|
|
||
|
#include "qbase.h"
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
#define LOCKQ_DEBUG
|
||
|
#endif
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
#ifndef LOCKQ_DEBUG
|
||
|
#define LOCKQ_DEBUG
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
//------------------------------------------------
|
||
|
class CQueueLockV1 {
|
||
|
//
|
||
|
// This class implements a queue which is multi-threaded safe for append operations.
|
||
|
// In addition, this queue will synchronize removal of elements amongst those
|
||
|
// threads which are appending to the queue. Each thread that appends to the
|
||
|
// queue potentially becomes the owner of the removal lock of the queue.
|
||
|
// (If Append() returns TRUE the thread owns the removal lock of the queue.)
|
||
|
// There is no way to relinquish the removal lock except to empty the queue.
|
||
|
// Emptying the queue must be done as a series of GetHead(), Remove() calls.
|
||
|
// A GetHead() call MUST preceed each Remove() call.
|
||
|
//
|
||
|
// Each time the thread calls GetHead, it will be
|
||
|
// told whether it still owns the removallock (when the queue is emptied the lock is
|
||
|
// relinquished.)
|
||
|
// Owning the removal lock in no way interferes with other threads appending to the queue.
|
||
|
//
|
||
|
// The class uses InterlockedExchange to handle all synchronization issues.
|
||
|
//
|
||
|
// For appending - InterlockedExchange does the following :
|
||
|
// Exchange the tail pointer with what we want to be the new tail.
|
||
|
// Make the old tail that we got from the exchange point to the new element.
|
||
|
// The list is now intact. Because there is an extra element in the queue
|
||
|
// (m_special) the tail pointer never becomes NULL.
|
||
|
//
|
||
|
// For synchronization safety -
|
||
|
// InterlockedExchange is used on the element next pointers in order to
|
||
|
// determine what thread has the removal lock.
|
||
|
// Each call to GetHead Exchanges the head's next pointer with LOCKVAL.
|
||
|
// Each Append call will also Exchange the old tail elements next value
|
||
|
// with the new m_pTail value. So there are only two threads which can
|
||
|
// be exchanging the next pointer at any given instant.
|
||
|
//
|
||
|
// The remove thread either gets 0 or a NON-NULL pointer after its Exchange.
|
||
|
//
|
||
|
// If it gets Zero, it knows it Exchanged before the Appending thread,
|
||
|
// in which case it loses the lock (since it can't safely do anything.)
|
||
|
// If it gets a Non-Zero value, the other thread exchanged first.
|
||
|
// In this case, the remover still has the lock and repairs the list.
|
||
|
//
|
||
|
// In the case of the appending thread - after its Exchange it either gets
|
||
|
// zero or LOCKVAL.
|
||
|
//
|
||
|
// If the appending thread gets zero, it Exchanged first so the other thread
|
||
|
// should hold onto the lock. If the appending thread gets LOCKVAL then it
|
||
|
// owns the lock.
|
||
|
//
|
||
|
// Finally, note that there is ALWAYS AT LEAST ONE ELEMENT in the Queue (m_special).
|
||
|
// This means that if there is anything of value in the queue at all then
|
||
|
// there must be at least TWO elements in the queue (and m_pHead->p is non null).
|
||
|
//
|
||
|
//
|
||
|
// Usage should be the following in each worker thread :
|
||
|
// CQueueLockV1 *pQueue ;
|
||
|
//
|
||
|
// if( pQueue->Append( p ) ) {
|
||
|
// CQueueElement *pWork ;
|
||
|
//
|
||
|
// while( pQueue->GetHead( pWork ) ) {
|
||
|
// /* Do work on pWork It is safe to do further calls to pQueue->Append()
|
||
|
// while doing whatever work. That does not mean those Appended
|
||
|
// elements will be processed on this thread. */
|
||
|
//
|
||
|
// pQueue->Remove() ;
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
private :
|
||
|
enum CONSTANTS {
|
||
|
LOCKVAL = -1,
|
||
|
} ;
|
||
|
|
||
|
CQElement m_special ; // element used ensure that the Queue always contains
|
||
|
// something.
|
||
|
CQElement m_release ; // element used with ReleaseLock calls to give up the
|
||
|
// RemovalLock on the Queue.
|
||
|
|
||
|
// This pointer is set after a call to ReleaseLock() - and
|
||
|
// will pointer to the queue element before the m_release element in
|
||
|
// the queue.
|
||
|
CQElement *m_pHead ;
|
||
|
CQElement *m_pTail ;
|
||
|
|
||
|
#ifdef LOCKQ_DEBUG
|
||
|
DWORD m_dwOwningThread ;
|
||
|
LONG m_lock ;
|
||
|
#endif
|
||
|
|
||
|
BOOL OfferOwnership( CQElement* p ) ;
|
||
|
CQElement* GetFront( ) ;
|
||
|
public :
|
||
|
CQueueLockV1( ) ;
|
||
|
~CQueueLockV1( ) ;
|
||
|
|
||
|
//
|
||
|
// Append - returns TRUE if we have added the first element into the queue
|
||
|
// and we now own the lock.
|
||
|
//
|
||
|
BOOL Append( CQElement *p ) ;
|
||
|
|
||
|
//
|
||
|
// Remove - takes the head element off the list. The head element should be
|
||
|
// examined with GetHead(). As long as we are calling GetHead() at least once
|
||
|
// before each call to Remove(), the synchronization aspects of the queue will
|
||
|
// work.
|
||
|
//
|
||
|
void Remove( ) ;
|
||
|
|
||
|
//
|
||
|
// GetHead - returns TRUE as long as there is an element to be had at the front
|
||
|
// of the queue. The element pointer is returned through the reference to a pointer.
|
||
|
//
|
||
|
BOOL GetHead( CQElement *&p ) ;
|
||
|
|
||
|
//
|
||
|
// RemoveRelease - takes the head element off the list.
|
||
|
// Also offers the removal lock to any other thread out there.
|
||
|
// If the function returns TRUE then this thread still has the lock,
|
||
|
// otherwise another thread has it.
|
||
|
//
|
||
|
BOOL RemoveAndRelease( ) ;
|
||
|
|
||
|
} ;
|
||
|
|
||
|
|
||
|
#ifndef _NO_TEMPLATES_
|
||
|
//--------------------------------------------------
|
||
|
template< class Element >
|
||
|
class TLockQueueV1 {
|
||
|
//
|
||
|
// This template is designed for use with Elements derived from CQElement.
|
||
|
// This will use the CQueueLockV1 class above to implement a locking queue containing
|
||
|
// elements of type 'Element'.
|
||
|
//
|
||
|
private :
|
||
|
CQueueLockV1 m_queue ;
|
||
|
public :
|
||
|
inline TLockQueueV1() ;
|
||
|
inline ~TLockQueueV1() ;
|
||
|
|
||
|
// Add an element to the Queue - if this returns TRUE we own the lock.
|
||
|
inline BOOL Append( Element* p ) ;
|
||
|
|
||
|
// remove an element from the Queue - lock ownership does not change.
|
||
|
inline void Remove( ) ;
|
||
|
inline BOOL GetHead( Element *&p ) ;
|
||
|
inline BOOL RemoveAndRelease( ) ;
|
||
|
} ;
|
||
|
#endif
|
||
|
|
||
|
#define DECLARE_LOCKQ( Type ) \
|
||
|
class LockQ ## Type { \
|
||
|
private : \
|
||
|
CQueueLockV1 m_queue ; \
|
||
|
public : \
|
||
|
LockQ ## Type () {} \
|
||
|
~LockQ ## Type () {} \
|
||
|
BOOL Append( Type * p ) { return m_queue.Append( (CQElement*)p ) ; } \
|
||
|
void Remove( ) { m_queue.Remove() ; } \
|
||
|
BOOL GetHead( Type *&p ) { \
|
||
|
CQElement* pTemp = 0; \
|
||
|
BOOL fTemp = m_queue.GetHead( pTemp ) ; \
|
||
|
p = (Type*)pTemp ; \
|
||
|
return fTemp ; \
|
||
|
} \
|
||
|
BOOL RemoveAndRelease( ) { \
|
||
|
return m_queue.RemoveAndRelease() ; \
|
||
|
} \
|
||
|
} ;
|
||
|
|
||
|
#define INVOKE_LOCKQ( Type ) LockQ ## Type
|
||
|
|
||
|
|
||
|
class CQueueLock {
|
||
|
//
|
||
|
// This class implements a queue which is multi-threaded safe for append operations.
|
||
|
// In addition, this queue will synchronize removal of elements amongst those
|
||
|
// threads which are appending to the queue. Each thread that appends to the
|
||
|
// queue potentially becomes the owner of the removal lock of the queue.
|
||
|
// (If Append() returns TRUE the thread owns the removal lock of the queue.)
|
||
|
// There is no way to relinquish the removal lock except to empty the queue.
|
||
|
//
|
||
|
// Usage should be the following in each worker thread :
|
||
|
// CQueueLock *pQueue ;
|
||
|
//
|
||
|
// if( pQueue->Append( p ) ) {
|
||
|
// CQueueElement *pWork ;
|
||
|
//
|
||
|
// while( (pWork = pQueue->RemoveAndRelease( )) ) {
|
||
|
// /* Do work on pWork It is safe to do further calls to pQueue->Append()
|
||
|
// while doing whatever work. That does not mean those Appended
|
||
|
// elements will be processed on this thread. */
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
private :
|
||
|
|
||
|
//
|
||
|
// Class constants - LOCKVAL that special value marking the queue element
|
||
|
// which is ready to be grabbed !
|
||
|
//
|
||
|
enum CONSTANTS {
|
||
|
LOCKVAL = -1,
|
||
|
} ;
|
||
|
|
||
|
//
|
||
|
// Element which always remains in the list !
|
||
|
//
|
||
|
CQElement m_special ;
|
||
|
|
||
|
//
|
||
|
// Head of the list
|
||
|
//
|
||
|
CQElement *m_pHead ;
|
||
|
|
||
|
//
|
||
|
// Tail of the list !
|
||
|
//
|
||
|
CQElement *m_pTail ;
|
||
|
|
||
|
public :
|
||
|
//
|
||
|
// Initialize the queue to an empty state -
|
||
|
// and also sets things up so that the first Append
|
||
|
// will own the lock !
|
||
|
//
|
||
|
inline CQueueLock( ) ;
|
||
|
|
||
|
//
|
||
|
// Also initializes to an empty state, however
|
||
|
// allows caller to specify whether queue can be
|
||
|
// grabbed on the first Append !
|
||
|
//
|
||
|
inline CQueueLock( BOOL fSet ) ;
|
||
|
|
||
|
#ifdef LOCKQ_DEBUG
|
||
|
//
|
||
|
// Check that the queue is emptied when it is destroyed !
|
||
|
//
|
||
|
inline ~CQueueLock( ) ;
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// Set the lock to the lockable state where the next
|
||
|
// call to Append() will acquire the lock !
|
||
|
// This function is not thread safe and should only
|
||
|
// be called when we are sure there is only one thread
|
||
|
// using the object !
|
||
|
//
|
||
|
inline void Reset() ;
|
||
|
|
||
|
//
|
||
|
// Append - returns TRUE if we have added the first element into the queue
|
||
|
// and we now own the lock.
|
||
|
//
|
||
|
inline BOOL Append( CQElement *p ) ;
|
||
|
|
||
|
//
|
||
|
// return the head of the Queue - if we return NULL then some other thread
|
||
|
// may own the lcok the queue implicitly implies !
|
||
|
//
|
||
|
inline CQElement* RemoveAndRelease( ) ;
|
||
|
|
||
|
//
|
||
|
// return the head of the Queue - but don't let any other threads
|
||
|
// grab the queue !
|
||
|
//
|
||
|
inline CQElement* Remove( ) ;
|
||
|
|
||
|
} ;
|
||
|
|
||
|
|
||
|
template< class Element >
|
||
|
class TLockQueue {
|
||
|
//
|
||
|
// This template is designed for use with Elements derived from CQElement.
|
||
|
// This will use the CQueueLock class above to implement a locking queue containing
|
||
|
// elements of type 'Element'.
|
||
|
//
|
||
|
private :
|
||
|
CQueueLock m_queue ;
|
||
|
|
||
|
public :
|
||
|
//
|
||
|
// Create an empty queue
|
||
|
//
|
||
|
inline TLockQueue() ;
|
||
|
|
||
|
//
|
||
|
// Create empty queue and specify whether the
|
||
|
// lock is initially available
|
||
|
//
|
||
|
inline TLockQueue( BOOL fSet ) ;
|
||
|
|
||
|
//
|
||
|
// Caller must already have lock and be only thread
|
||
|
// using object - make the lock available !
|
||
|
//
|
||
|
inline void Reset() ;
|
||
|
|
||
|
//
|
||
|
// Add an element to the Queue - if this returns TRUE we own the lock.
|
||
|
//
|
||
|
inline BOOL Append( Element* p ) ;
|
||
|
|
||
|
//
|
||
|
// remove an element from the Queue - lock ownership does not change unless this
|
||
|
// returns NULL !
|
||
|
//
|
||
|
inline Element* RemoveAndRelease( ) ;
|
||
|
|
||
|
//
|
||
|
// remove an element from the Queue - but don't relinquish lock !
|
||
|
//
|
||
|
inline Element* Remove() ;
|
||
|
} ;
|
||
|
|
||
|
#include "lockq.inl"
|
||
|
|
||
|
#endif // _LOCKQ_H_
|
||
|
|