|
|
/*++
Copyright (C) 2001 Microsoft Corporation
Module Name:
THRPOOL.H
Abstract:
General purpose thread pool.
History:
raymcc 25-Feb-99 Created
--*/
/*
(1) Flexible thread pool size. Starts at zero and only creates threads as required. (2) Threads wait to be reused and if not reused within the alloted time, they self-terminate. Therefore, the thread pool drops to zero within the specified timeout if no activity occurs. (3) To avoid creating a lot of threads quickly, a request will attempt to wait a small amount of time for a thread to free up before requesting a new thread creation. */
#include "precomp.h"
#include <stdio.h>
#include <assert.h>
#include "corepol.h"
#include <thrpool.h>
#define THREAD_STACK_SIZE 0x2000 /* 8K */
//***************************************************************************
//
// CThreadPool::CThreadPool
//
//***************************************************************************
// ok
CThreadPool::CThreadPool( DWORD dwMsMaxIdleBeforeDie, LONG lIdleThreadLimit, LONG lMaxThreads ) { m_dwMsMaxIdle = dwMsMaxIdleBeforeDie; m_lMaxThreads = lMaxThreads; m_lIdleThreadLimit = lIdleThreadLimit;
m_lTotalThreads = 0; m_lIdleThreads = 0;
m_hReleaseThread = CreateEvent(0, 0, 0, 0); m_hBeginRendezvous = CreateEvent(0, 0, 0, 0); m_hParmXfer = CreateEvent(0, 0, 0, 0); m_hEndRendezvous = CreateEvent(0, 0, 0, 0);
InitializeCriticalSection(&m_cs_dispatch); InitializeCriticalSection(&m_cs_pool);
m_bPendingRequest = false; m_bShutdown = false;
m_pXferUserParms = 0; m_pXferUserProc = 0; m_pXferReturnCode = 0; m_pXferElapsedTime = 0; }
//***************************************************************************
//
// CThreadPool::~CThreadPool
//
//***************************************************************************
// ok
CThreadPool::~CThreadPool() { bool bRes = Shutdown(); // Let all threads terminate
if (bRes) { CloseHandle(m_hReleaseThread); CloseHandle(m_hBeginRendezvous); CloseHandle(m_hParmXfer); CloseHandle(m_hEndRendezvous);
DeleteCriticalSection(&m_cs_dispatch); DeleteCriticalSection(&m_cs_pool); } // Else we will have to leak, since all the threads didn't shut down.
}
//***************************************************************************
//
// CThreadPool::_ThreadEntry()
//
// Win32 entry point, which wraps and call the per-object entry point.
// We do this because Win32 cannot easily call an entry point in a C++
// instance unless we use various weird calling convention override
// tricks, which I don't remember right now. :)
//
//***************************************************************************
// ok
DWORD WINAPI CThreadPool::_ThreadEntry(LPVOID pArg) { CThreadPool *pThis = (CThreadPool *) pArg; pThis->Pool(); return 0; // Not used
}
//***************************************************************************
//
// CThreadPool::Pool
//
// The basic pool is implemented here. All active threads remain in this
// loop. Any threads which get too old waiting for work are allowed to
// retire. New threads are created from the DispatchThread() member.
//
// Note that the <m_lTotalThreads> and <m_lIdleThreads> are not part
// of the algorithm; they are simply there for statistical purposes.
//
//***************************************************************************
// ok
void CThreadPool::Pool() { InterlockedIncrement(&m_lTotalThreads);
// Pool.
// =====
for (;;) { if (m_bShutdown) break;
if (m_lIdleThreadLimit != -1 && m_lIdleThreads > m_lIdleThreadLimit) break;
// Wait for either the thread to be idle or else until a new
// request causes it to be released.
// =========================================================
InterlockedIncrement(&m_lIdleThreads);
DWORD dwRes = WaitForSingleObject(m_hReleaseThread, m_dwMsMaxIdle);
// We don't know why the thread was released. Maybe it was tired
// of waiting or maybe the user is shutting down our little operation.
// ===================================================================
if (dwRes == WAIT_TIMEOUT || m_bShutdown) { InterlockedDecrement(&m_lIdleThreads); break; }
// If here, we now enter the pool critical section and attempt
// to enter into a synchronized rendezvous with the dispacher.
// ============================================================
EnterCriticalSection(&m_cs_pool);
if (m_bPendingRequest) { InterlockedDecrement(&m_lIdleThreads);
SetEvent(m_hBeginRendezvous); WaitForSingleObject(m_hParmXfer, INFINITE);
// Copy the parameters that the user specified in
// DispatchThread(). We cannot be here unless
// DispatchThread() signaled us.
// ==============================================
LPVOID pUserArg = m_pXferUserParms; LPTHREAD_START_ROUTINE pUserEntry = m_pXferUserProc; LPDWORD pElapsed = m_pXferElapsedTime; LPDWORD pRetVal = m_pXferReturnCode; HANDLE hCompleted = m_hXferThreadCompleted; m_bPendingRequest = false;
// No longer in 'request' mode, so a new dispatch can occur.
// Note that the user's parms are safely in local variables.
// Allow further dispatches to occur.
// ========================================================
SetEvent(m_hEndRendezvous);
LeaveCriticalSection(&m_cs_pool);
// If user wants elapsed time, record start time.
// ==============================================
DWORD dwStart = 0; if (m_pXferElapsedTime) dwStart = GetCurrentTime();
// Call user's entry point.
// ========================
DWORD dwResToRet = pUserEntry(pUserArg);
// If user wants return value, forward it.
// =======================================
if (pRetVal) *pRetVal = dwResToRet;
// If user wants elapsed time, record stop time
// and forward elapsed time.
// ============================================
if (m_pXferElapsedTime) *m_pXferElapsedTime = GetCurrentTime() - dwStart;
// If user wants event signaled, do it.
// ====================================
if (hCompleted) SetEvent(hCompleted);
}
// If here, somehow the event got signaled and nobody wanted
// the thread. This could occur if somebody executed Shutdown()
// and then Restart() before Shutdown() was finished.
// Simply return to top of loop.
// =============================================================
else { LeaveCriticalSection(&m_cs_pool); InterlockedDecrement(&m_lIdleThreads); } }
InterlockedDecrement(&m_lTotalThreads); // printf("---Thread Ending--- 0x%X\n", GetCurrentThreadId());
}
//***************************************************************************
//
// CThreadPool::Shutdown
//
// Alas, the pool is doomed and must be drained. Inform each thread of
// its fate.
//
//***************************************************************************
// ok
bool CThreadPool::Shutdown(DWORD dwMaxWait) { m_bShutdown = true; m_bPendingRequest = false;
DWORD dwStart = GetCurrentTime();
while (m_lTotalThreads && m_bShutdown) // No need to protect this
{ SetEvent(m_hReleaseThread); // Allow thread to realize it is doomed
if (GetCurrentTime() - dwStart > dwMaxWait) break; Sleep(10); }
if (m_lTotalThreads == 0) return true;
return false; }
//***************************************************************************
//
// CThreadPool::Restart
//
// Allow a thread pool to get back into business again.
//
//***************************************************************************
// ok
bool CThreadPool::Restart() { m_bShutdown = false; return true; }
//***************************************************************************
//
// CThreadPool::CreateNewThread
//
// Helper to create a new thread.
//
//***************************************************************************
// ok
bool CThreadPool::CreateNewThread() { DWORD dwId;
HANDLE hThread = CreateThread( 0, // Security
THREAD_STACK_SIZE, // 8k stack
_ThreadEntry, // Thread proc address
LPVOID(this), // Thread parm
0, // Flags
&dwId );
if (hThread == NULL) return false;
// printf("--Created Thread 0x%X\n", dwId);
CloseHandle(hThread); return true; }
//***************************************************************************
//
// CThreadPool::DispatchThread
//
// Called by the user to dispatch a thread to an entry point.
//
// Returns:
// NoError -- Success
// ExceededPool -- No threads available within the timeout.
// Shutdown -- Thread pool is being shut down
// ThreadCreationFailure -- Couldn't create a thread.
//
//***************************************************************************
// ok
int CThreadPool::DispatchThread( IN DWORD dwMaxPreferredWait, IN DWORD dwMaxWaitBeforeFail, IN LPTHREAD_START_ROUTINE pEntry, IN LPVOID pArg, IN DWORD *pdwReturnCode, IN DWORD *pdwElapsedTime, IN HANDLE hThreadCompleted ) { DWORD dwRes;
// Don't allow new requests during a shutdown.
// ===========================================
if (m_bShutdown) return ShutdownInProgress;
// Serialize all access to thread acquisition.
// ===========================================
EnterCriticalSection(&m_cs_dispatch);
// If zero threads, create one.
// ============================
if (m_lTotalThreads == 0) { if (CreateNewThread() != true) { LeaveCriticalSection(&m_cs_dispatch); return ThreadCreationFailure; } }
// Flag for the pool to look at to determine
// if a dispatch is in progress.
// =========================================
m_bPendingRequest = true;
// Release at least one thread. If there aren't any
// available, this is harmless. Likewise, if this
// releases threads which don't in fact become acquired
// by this dispatch, again it is harmless.
// ====================================================
SetEvent(m_hReleaseThread);
// We now try to rendevous with a thread within the
// dwMaxPreferredWait time by waiting for a 'thready ready'
// event from the thread pool. If no thread responds,
// because they are busy, we will timeout.
// ========================================================
dwRes = WaitForSingleObject(m_hBeginRendezvous, dwMaxPreferredWait);
if (dwRes == WAIT_OBJECT_0) { // We are now in a synchronized rendevous with the thread pool.
// Next, make the interthread parameter transfer.
// ============================================================
m_pXferUserParms = pArg; m_pXferUserProc = pEntry; m_pXferElapsedTime = pdwElapsedTime; m_pXferReturnCode = pdwReturnCode; m_hXferThreadCompleted = hThreadCompleted;
m_bPendingRequest = false; // No longer needed
// Now, release the thread pool thread to grab the parameters.
// ===========================================================
SetEvent(m_hParmXfer);
WaitForSingleObject(m_hEndRendezvous, INFINITE);
LeaveCriticalSection(&m_cs_dispatch); // Allow more dispatches
return NoError; }
if (m_bShutdown) { LeaveCriticalSection(&m_cs_dispatch); return ShutdownInProgress; }
// If here, we didn't acquire a thread. Let's create another one.
// If, in the meantime, the event shows up anyway (and we simply
// didn't wait long enough!), then we ended up with another thread.
// So what? If one is good, two is better.
// ================================================================
if (m_lMaxThreads == -1 || (m_lTotalThreads < m_lMaxThreads)) { if (CreateNewThread() != true) { LeaveCriticalSection(&m_cs_dispatch); return ThreadCreationFailure; } }
if (m_bShutdown) { LeaveCriticalSection(&m_cs_dispatch); return ShutdownInProgress; }
// Now, wait for that same old event, indicating we have begun
// a synchronized rendevous with the thread pool.
// ============================================================
dwRes = WaitForSingleObject(m_hBeginRendezvous, dwMaxWaitBeforeFail);
if (m_bShutdown) { LeaveCriticalSection(&m_cs_dispatch); return ShutdownInProgress; }
if (dwRes == WAIT_OBJECT_0) { // We are now in a synchronized rendevous with the thread pool.
// Next, make the interthread parameter transfer.
// ============================================================
m_pXferUserParms = pArg; m_pXferUserProc = pEntry; m_pXferElapsedTime = pdwElapsedTime; m_pXferReturnCode = pdwReturnCode; m_hXferThreadCompleted = hThreadCompleted;
m_bPendingRequest = false; // No longer needed
// Now, release the thread pool thread to grab the parameters.
// ===========================================================
SetEvent(m_hParmXfer);
WaitForSingleObject(m_hEndRendezvous, INFINITE);
LeaveCriticalSection(&m_cs_dispatch); // Allow more dispatches
return NoError; }
// If here, we simply ran out of time. If in the meantime
// the event gets signaled, it will benefit any new thread coming
// in looking for a request!
// ==============================================================
m_bPendingRequest = false; // We may be too late, but it looks good anyway.
LeaveCriticalSection(&m_cs_dispatch);
return ExceededPool; }
|