The next class,
CMclLinkedList, is a sophisticated, high level class built out of the basic Mcl wrapper classes. Unlike in single threaded programming, in multithreaded programming access to a linked list must be coordinated; two threads must not modify the list’s state at the same time. CMclLinkedList objects and classes derived from CMclLinkedList can only be used to share ordered data between threads within a single process. To coordinate the sharing of ordered data between threads in different processes, use the CMclMailbox class, described next.The
CMclLinkedList class uses the CMclCritSec and CMclSemaphore wrapper classes to create a thread safe list which provides thread access synchronization within the access member functions. Providing synchronization within the member functions which modify the internal state of the object is known as internal synchronization. Internal, as well as external synchronization will be discussed further in Chapter 9.This is the only pure template class in the CMcl library. The implementation is contained entirely in the CMclLinkedLists.h header file shown in Example 7-4.
CMclLinkedList is written using templates so that the data operations will work with any type, built-in or user-defined. The only requirement of the data type to be stored in the linked list is that it must implement the copy constructor and assignment operators. For many data types, the default bitwise copy and bitwise assignment operations are fine. More complex types can usually be implemented easily by the user of the linked list class.Two common structures based on linked lists, stacks and queues, are implemented as template classes derived from
CMclLinkedList. These are CMclStack and CMclQueue. They have the same requirements on the data parameter class as the base class.Example 7-4. CMclLinkedLists.h, the linked list template class header and implementation file
//
// FILE: CMclLinkedLists.h
//
// Copyright (c) 1997 by Aaron Michael Cohen and Mike Woodring
//
/////////////////////////////////////////////////////////////////////////
#ifndef __CMCLLINKEDLISTS_H__
#define __CMCLLINKEDLISTS_H__
#include "CMclGlobal.h"
#include "CMclSemaphore.h"
#include "CMclCritSec.h"
// this linked list template class can be instantiated for
// any type which has an accessable copy constructor and
// assignment operator...
template <class T>
class CMclLinkedList {
protected:
// abstract node class...
class CMclLinkedListNode {
public:
CMclLinkedListNode *m_pNext;
CMclLinkedListNode *m_pPrev;
virtual void SetData( T & rData) = 0;
virtual void GetData( T & rData) = 0;
};
// derived class for master and free list nodes...
class CMclLinkedListSentinelNode : public CMclLinkedListNode {
public:
virtual void SetData( T & rData) {
// this function should never be called...
CMclThrowError(ERROR_ACCESS_DENIED);
};
virtual void GetData( T & rData) {
// this function should never be called...
CMclThrowError(ERROR_ACCESS_DENIED);
};
};
// derived class for data nodes...
class CMclLinkedListDataNode : public CMclLinkedListNode {
public:
T m_data;
CMclLinkedListDataNode( T & rData) : m_data(rData) {
return;
};
virtual void SetData( T & rData) {
m_data = rData;
};
virtual void GetData( T & rData) {
rData = m_data;
};
};
CMclLinkedListSentinelNode m_MasterNode;
CMclLinkedListSentinelNode m_FreeNode;
CMclCritSec m_cCritSec;
CMclSemaphore m_csNotEmpty;
DWORD m_dwStatus;
public:
CMclLinkedList() : m_dwStatus(NO_ERROR),
m_csNotEmpty( 0, 0x7FFFFFFF), m_cCritSec() {CMCL_CHECK_CREATION_STATUS(
m_csNotEmpty.Status(), m_dwStatus);// both the master and free nodes point to themselves
// to indicate that the lists are empty...
m_MasterNode.m_pNext = m_MasterNode.m_pPrev = &m_MasterNode;
m_FreeNode.m_pNext = m_FreeNode.m_pPrev = &m_FreeNode;
};
~CMclLinkedList() {
Cleanup();
};
BOOL PutOnHeadOfList( T & rData) {
// acquire the list critical section lock...
CMclAutoLock autoLock(m_cCritSec);
// get a free list node and attach the data to it...
CMclLinkedListNode *pNewNode = AllocateListNode(rData);
if (pNewNode == NULL) {
// this is a memory allocation failure...
CMclThrowError(ERROR_OUTOFMEMORY);
return FALSE;
}
// put the node at the head of the list...
pNewNode->m_pNext = m_MasterNode.m_pNext;
m_MasterNode.m_pNext->m_pPrev = pNewNode;
pNewNode->m_pPrev = &m_MasterNode;
m_MasterNode.m_pNext = pNewNode;
// add one to the semaphore count which tracks the
// number of elements in the list...
m_csNotEmpty.Release(1);
return TRUE;
};
BOOL PutOnTailOfList( T & rData) {
// acquire the list critical section lock...
CMclAutoLock autoLock(m_cCritSec);
// get a free list node and attach the data to it...
CMclLinkedListNode *pNewNode = AllocateListNode(rData);
if (pNewNode == NULL) {
// this is a memory allocation failure...
CMclThrowError(ERROR_OUTOFMEMORY);
return FALSE;
}
// put the node at the tail of the list...
m_MasterNode.m_pPrev->m_pNext = pNewNode;
pNewNode->m_pPrev = m_MasterNode.m_pPrev;
m_MasterNode.m_pPrev = pNewNode;
pNewNode->m_pNext = &m_MasterNode;
// add one to the semaphore count which tracks the
// number of elements in the list...
m_csNotEmpty.Release(1);
return TRUE;
};
BOOL GetFromHeadOfList( T & rData, DWORD dwTimeout, CMclEvent *pInterrupt = NULL) {
// wait until there is an element on the list or the
// interrupt event is signaled...
if (pInterrupt) {
m_dwStatus = pInterrupt->WaitForTwo(
m_csNotEmpty, FALSE, dwTimeout);if (!CMclWaitSucceeded(m_dwStatus, 2))
return FALSE;
if (CMclWaitSucceededIndex(m_dwStatus) == 0)
return FALSE;
}
else {
m_dwStatus =
m_csNotEmpty.Wait(dwTimeout);if (!CMclWaitSucceeded(m_dwStatus, 1))
return FALSE;
}
// acquire the list critical section lock...
CMclAutoLock autoLock(m_cCritSec);
// take the node off the head of the list...
CMclLinkedListNode *pNode = m_MasterNode.m_pNext;
m_MasterNode.m_pNext = pNode->m_pNext;
pNode->m_pNext->m_pPrev = &m_MasterNode;
// copy the data from the list node...
pNode->GetData(rData);
// add the list node to the free list...
AddToFreeList(pNode);
// all done...
return TRUE;
};
BOOL GetFromTailOfList( T & rData, DWORD dwTimeout, CMclEvent *pInterrupt = NULL) {
// wait until there is an element on the list or the
// interrupt event is signaled...
if (pInterrupt) {
m_dwStatus = pInterrupt->WaitForTwo(
m_csNotEmpty, FALSE, dwTimeout);if (!CMclWaitSucceeded(m_dwStatus, 2))
return FALSE;
if (CMclWaitSucceededIndex(m_dwStatus) == 0)
return FALSE;
}
else {
m_dwStatus =
m_csNotEmpty.Wait(dwTimeout);if (!CMclWaitSucceeded(m_dwStatus, 1))
return FALSE;
}
// acquire the list critical section lock...
CMclAutoLock autoLock(m_cCritSec);
// take the node off the tail of the list...
CMclLinkedListNode *pNode = m_MasterNode.m_pPrev;
m_MasterNode.m_pPrev = pNode->m_pPrev;
pNode->m_pPrev->m_pNext = &m_MasterNode;
// copy the data from the list node...
pNode->GetData(rData);
// add the list node to the free list...
AddToFreeList(pNode);
// all done...
return TRUE;
};
DWORD Status(void) {
return m_dwStatus;
};
protected:
void AddToFreeList(CMclLinkedListNode *pFreeNode) {
// attach node to the end of the free list...
m_FreeNode.m_pPrev->m_pNext = pFreeNode;
pFreeNode->m_pPrev = m_FreeNode.m_pPrev;
m_FreeNode.m_pPrev = pFreeNode;
pFreeNode->m_pNext = &m_FreeNode;
};
CMclLinkedListNode *AllocateListNode( T & rData) {
// grab a node off the free list, or create
// a new one if none are available...
CMclLinkedListNode *pNode = m_FreeNode.m_pNext;
if (pNode != &m_FreeNode) {
pNode->m_pPrev->m_pNext = pNode->m_pNext;
pNode->m_pNext->m_pPrev = pNode->m_pPrev;
pNode->m_pPrev = pNode;
pNode->m_pNext = pNode;
pNode->SetData(rData);
}
else {
pNode = new CMclLinkedListDataNode(rData);
}
return pNode;
};
void Cleanup(void) {
// delete all of the list nodes on the master list...
CMclLinkedListNode *pNode = m_MasterNode.m_pNext;
while (pNode != &m_MasterNode) {
CMclLinkedListNode *pOldNode = pNode;
pNode = pNode->m_pNext;
delete pOldNode;
}
// delete all of the list nodes on the free list...
pNode = m_FreeNode.m_pNext;
while (pNode != &m_FreeNode) {
CMclLinkedListNode *pOldNode = pNode;
pNode = pNode->m_pNext;
delete pOldNode;
}
};
};
template <class T>
class CMclQueue : private CMclLinkedList<T> {
public:
BOOL Put( T & rData) {
return PutOnTailOfList(rData);
};
BOOL Get( T & rData, DWORD dwTimeout = INFINITE, CMclEvent *pInterrupt = NULL) {
return GetFromHeadOfList( rData, dwTimeout, pInterrupt);
};
DWORD Status(void) {
return CMclLinkedList<T>::Status();
}
};
template <class T>
class CMclStack : private CMclLinkedList<T> {
public:
BOOL Push( T & rData) {
return PutOnHeadOfList(rData);
};
BOOL Pop( T & rData, DWORD dwTimeout = INFINITE, CMclEvent *pInterrupt = NULL) {
return GetFromHeadOfList( rData, dwTimeout, pInterrupt);
};
DWORD Status(void) {
return CMclLinkedList<T>::Status();
}
};
#endif
The operations used to place data onto the linked list are PutOnHeadOfList and PutOnTailOfList. These always succeed immediately unless there is a memory allocation failure. If you have compiled the library with exceptions disabled, you should check that these functions return
TRUE.The get operations, which take data off of the lists, are more complex. If there is no data on the list, GetFromHeadOfList and GetFromTailOfList will wait a specified period of time for data to appear. If none does, the functions return
FALSE. These operations also have an optional third parameter, pInterrupt, which is a pointer to a kernel event wrapper object. If this event is signaled, the get operations will immediately return FALSE, even if the linked list is not empty. This provides an interruptible mechanism to wait for data to be placed in the linked list. Notice that the pointer to the interrupt wrapper object is the highest priority object waited on. This is necessary to ensure that the Get operations can be interrupted even if data is already in the queue.The
CMclLinkedList base class provides internal synchronization using the critical section object m_cCritSec, and the semaphore object m_csNotEmpty.The critical section serializes access to the internal data structures of the linked list; without it, nodes could be dropped during simultaneous puts and gets, and chaos would result. The semaphore is used to track the number of elements in the list. As long as there is at least one item on the list, the semaphore count will be greater than zero and a get operation waiting on the semaphore will return immediately. When the list is empty, waiting on the semaphore allows the caller to block until an item is put on the list.