原子访问、调整缓存行、高级线程同步、关键段、Slim读/写锁、条件变量
AcquireSRWLockExclusive
AcquireSRWLockShared
SleepConditionVariableSRW
/******************************************************************************
Module: Queue.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include <Windows.h>
#include <process.h>
#include <windowsx.h>
#include <tchar.h>
#include <StrSafe.h>
#include <iostream>
typedef unsigned (__stdcall *PTHREAD_START) (void *);
#define chBEGINTHREADEX(psa, cbStackSize, pfnStartAddr, \
pvParam, dwCreateFlags, pdwThreadId) \
((HANDLE)_beginthreadex( \
(void *) (psa), \
(unsigned) (cbStackSize), \
(PTHREAD_START) (pfnStartAddr), \
(void *) (pvParam), \
(unsigned) (dwCreateFlags), \
(unsigned *) (pdwThreadId)))
///////////////////////////////////////////////////////////////////////////////
class CQueue {
public:
struct ELEMENT {
int m_nThreadNum;
int m_nRequestNum;
// Other element data should go here
};
typedef ELEMENT* PELEMENT;
private:
struct INNER_ELEMENT {
int m_nStamp; // 0 means empty
ELEMENT m_element;
};
typedef INNER_ELEMENT* PINNER_ELEMENT;
private:
PINNER_ELEMENT m_pElements; // Array of elements to be processed
int m_nMaxElements; // Maximum # of elements in the array
int m_nCurrentStamp; // Keep track of the # of added elements
private:
int GetFreeSlot();
int GetNextSlot(int nThreadNum);
public:
CQueue(int nMaxElements);
~CQueue();
BOOL IsFull();
BOOL IsEmpty(int nThreadNum);
void AddElement(ELEMENT e);
BOOL GetNewElement(int nThreadNum, ELEMENT& e);
};
///////////////////////////////////////////////////////////////////////////////
CQueue::CQueue(int nMaxElements) {
// Allocate and initialize the elements
m_pElements = (PINNER_ELEMENT)
HeapAlloc(GetProcessHeap(), 0, sizeof(INNER_ELEMENT) * nMaxElements);
ZeroMemory(m_pElements, sizeof(INNER_ELEMENT) * nMaxElements);
// Initialize the element counter
m_nCurrentStamp = 0;
// Remember the max number of elements
m_nMaxElements = nMaxElements;
}
CQueue::~CQueue() {
HeapFree(GetProcessHeap(), 0, m_pElements);
}
BOOL CQueue::IsFull() {
return(GetFreeSlot() == -1);
}
BOOL CQueue::IsEmpty(int nThreadNum) {
return(GetNextSlot(nThreadNum) == -1);
}
int CQueue::GetFreeSlot() {
// Look for the first element with a 0 stamp
for(int current = 0; current < m_nMaxElements; current++) {
if (m_pElements[current].m_nStamp == 0)
return(current);
}
// No free slot was found
return(-1);
}
int CQueue::GetNextSlot(int nThreadNum) {
// By default, there is no slot for this thread
int firstSlot = -1;
// The element can't have a stamp higher than the last added
int firstStamp = m_nCurrentStamp+1;
// Look for the even (thread 0) / odd (thread 1) element that is not free
for(int current = 0; current < m_nMaxElements; current++) {
// Keep track of the first added (lowest stamp) in the queue
// --> so that "first in first out" behavior is ensured
if ((m_pElements[current].m_nStamp != 0) && // free element
((m_pElements[current].m_element.m_nRequestNum % 2) == nThreadNum) &&
(m_pElements[current].m_nStamp < firstStamp)) {
firstStamp = m_pElements[current].m_nStamp;
firstSlot = current;
}
}
return(firstSlot);
}
void CQueue::AddElement(ELEMENT e) {
// Do nothing if the queue is full
int nFreeSlot = GetFreeSlot();
if (nFreeSlot == -1)
return;
// Copy the content of the element
m_pElements[nFreeSlot].m_element = e;
// Mark the element with the new stamp
m_pElements[nFreeSlot].m_nStamp = ++m_nCurrentStamp;
}
BOOL CQueue::GetNewElement(int nThreadNum, ELEMENT& e) {
int nNewSlot = GetNextSlot(nThreadNum);
if (nNewSlot == -1)
return(FALSE);
// Copy the content of the element
e = m_pElements[nNewSlot].m_element;
// Mark the element as read
m_pElements[nNewSlot].m_nStamp = 0;
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
CQueue g_q(10); // The shared queue
volatile LONG g_fShutdown;// Signals client/server threads to die
HWND g_hWnd; // How client/server threads give status
SRWLOCK g_srwLock; // Reader/writer lock to protect the queue
CONDITION_VARIABLE g_cvReadyToConsume; // Signaled by writers
CONDITION_VARIABLE g_cvReadyToProduce; // Signaled by readers
// Handles to all reader/writer threads
HANDLE g_hThreads[MAXIMUM_WAIT_OBJECTS];
// Number of reader/writer threads
int g_nNumThreads = 0;
///////////////////////////////////////////////////////////////////////////////
void AddText(PCTSTR pszFormat, ...) {
va_list argList;
va_start(argList, pszFormat);
TCHAR sz[20 * 1024];
_vstprintf_s(sz, _countof(sz), pszFormat, argList);
std::wcout<<sz<<std::endl;
va_end(argList);
}
BOOL ConsumeElement(int nThreadNum, int nRequestNum) {
// Get access to the queue to consume a new element
AcquireSRWLockShared(&g_srwLock);
// Fall asleep until there is something to read.
// Check if, while it was asleep,
// it was not decided that the thread should stop
while (g_q.IsEmpty(nThreadNum) && !g_fShutdown) {
// There was not a readable element
AddText(TEXT("Consume thread[%d] Nothing to process"), nThreadNum);
// The queue is empty
// --> Wait until a writer adds a new element to read
// and come back with the lock acquired in shared mode
SleepConditionVariableSRW(&g_cvReadyToConsume, &g_srwLock,
INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED);
}
// When thread is exiting, the lock should be released for writer
// and readers should be signaled through the condition variable
if (g_fShutdown) {
// Show that the current thread is exiting
AddText(TEXT("Consume thread[%d] bye bye"), nThreadNum);
// Another writer thread might still be blocked on the lock
// --> release it before exiting
ReleaseSRWLockShared(&g_srwLock);
// Notify other readers that it is time to exit
// --> release readers
WakeConditionVariable(&g_cvReadyToConsume);
return(FALSE);
}
// Get the first new element
CQueue::ELEMENT e;
// Note: No need to test the return value since IsEmpty
// returned FALSE
g_q.GetNewElement(nThreadNum, e);
// No need to keep the lock any longer
ReleaseSRWLockShared(&g_srwLock);
// Show result of consuming the element
AddText(TEXT("Consume thread[%d] Processing Generate thread[%d]:%d"),
nThreadNum, e.m_nThreadNum, e.m_nRequestNum);
// A free slot is now available for writer threads to produce
// --> wake up a writer thread
WakeConditionVariable(&g_cvReadyToProduce);
return(TRUE);
}
DWORD WINAPI ReaderThread(PVOID pvParam) {
int nThreadNum = PtrToUlong(pvParam);
for (int nRequestNum = 1; !g_fShutdown; nRequestNum++) {
if (!ConsumeElement(nThreadNum, nRequestNum))
return(0);
Sleep(2500); // Wait before reading another element
}
// g_fShutdown has been set during Sleep
// --> Show that the current thread is exiting
AddText(TEXT("Generate thread[%d] bye bye"), nThreadNum);
return(0);
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI WriterThread(PVOID pvParam) {
int nThreadNum = PtrToUlong(pvParam);
for (int nRequestNum = 1; !g_fShutdown; nRequestNum++) {
CQueue::ELEMENT e = { nThreadNum, nRequestNum };
// Require access for writing
AcquireSRWLockExclusive(&g_srwLock);
// If the queue is full, fall asleep as long as the condition variable
// is not signaled
// Note: During the wait for acquiring the lock,
// a stop might have been received
if (g_q.IsFull() & !g_fShutdown) {
// No more room in the queue
AddText(TEXT("Generate thread[%d] Queue is full: impossible to add %d"),
nThreadNum, nRequestNum);
// --> Need to wait for a reader to empty a slot before acquiring
// the lock again
SleepConditionVariableSRW(&g_cvReadyToProduce, &g_srwLock,
INFINITE, 0);
}
// Other writer threads might still be blocked on the lock
// --> Release the lock and notify the remaining writer threads to quit
if (g_fShutdown) {
// Show that the current thread is exiting
AddText(TEXT("Generate thread[%d] bye bye"), nThreadNum);
// No need to keep the lock any longer
ReleaseSRWLockExclusive(&g_srwLock);
// Signal other blocked writers threads that it is time to exit
WakeAllConditionVariable(&g_cvReadyToProduce);
// Bye bye
return(0);
} else {
// Add the new ELEMENT into the queue
g_q.AddElement(e);
Sleep(1500);
// Show result of processing element
AddText(TEXT("Generate thread[%d] Adding %d"), nThreadNum, nRequestNum);
// No need to keep the lock any longer
ReleaseSRWLockExclusive(&g_srwLock);
// Signal reader threads that there is an element to consume
WakeAllConditionVariable(&g_cvReadyToConsume);
// Wait before adding a new element
Sleep(1500);
}
}
// Show that the current thread is exiting
AddText(TEXT("Generate thread[%d] bye bye"), nThreadNum);
return(0);
}
///////////////////////////////////////////////////////////////////////////////
void init()
{
// Prepare the SRWLock to be used
InitializeSRWLock(&g_srwLock);
// Prepare the condition variables to be used
InitializeConditionVariable(&g_cvReadyToConsume);
InitializeConditionVariable(&g_cvReadyToProduce);
// Will be set to TRUE in order to end threads
g_fShutdown = FALSE;
// Create the writer threads
DWORD dwThreadID;
for (int x = 0; x < 4; x++)
g_hThreads[g_nNumThreads++] =
chBEGINTHREADEX(NULL, 0, WriterThread, (PVOID) (INT_PTR) x,
0, &dwThreadID);
// Create the reader threads
for (int x = 0; x < 2; x++)
g_hThreads[g_nNumThreads++] =
chBEGINTHREADEX(NULL, 0, ReaderThread, (PVOID) (INT_PTR) x,
0, &dwThreadID);
WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);
}
///////////////////////////////////////////////////////////////////////////////
void uninit() {
if (!g_fShutdown) {
// Ask all threads to end
InterlockedExchange(&g_fShutdown, TRUE);
// Free all threads waiting on condition variables
WakeAllConditionVariable(&g_cvReadyToConsume);
WakeAllConditionVariable(&g_cvReadyToProduce);
// Wait for all the threads to terminate & then clean up
WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);
// Don't forget to clean up kernel resources
// Note: This is not really mandatory since the process is exiting
while (g_nNumThreads--)
CloseHandle(g_hThreads[g_nNumThreads]);
}
}
int main()
{
init();
uninit();
return(0);
}
//////////////////////////////// End of File //////////////////////////////////