Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

667 lines
16 KiB

/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
tpswork.h
Abstract:
Worker thread classes. Moved out of tpsclass.h
Contents:
CIoWorkerThreadInfo
CIoWorkerRequest
CThreadPool
Author:
Richard L Firth (rfirth) 08-Aug-1998
Revision History:
08-Aug-1998 rfirth
Created
--*/
//
// manifests
//
#define THREAD_CREATION_DAMPING_TIME 5000
#define NEW_THREAD_THRESHOLD 10
#define MIN_WORKER_THREADS 1
#define MAX_WORKER_THREADS 128
#define MAX_IO_WORKER_THREADS 256
#define MAX_QUEUE_DEPTH 0
#define THREAD_IDLE_TIMEOUT 60000
#define TPS_ID 0x80000000
//
// external data
//
extern DWORD g_dwWorkItemId;
//
// classes
//
//
// CIoWorkerThreadInfo
//
class CIoWorkerThreadInfo : public CDoubleLinkedListEntry {
private:
HANDLE m_hThread;
public:
CIoWorkerThreadInfo(CDoubleLinkedList * pList) {
m_hThread = (HANDLE)-1;
InsertHead(pList);
}
~CIoWorkerThreadInfo() {
ASSERT(m_hThread == NULL);
}
VOID SetHandle(HANDLE hThread) {
m_hThread = hThread;
}
HANDLE GetHandle(VOID) const {
return m_hThread;
}
};
//
// CIoWorkerRequest
//
class CIoWorkerRequest {
private:
LPTHREAD_START_ROUTINE m_pfnCallback;
LPVOID m_pContext;
public:
CIoWorkerRequest(LPTHREAD_START_ROUTINE pfnCallback, LPVOID pContext) {
m_pfnCallback = pfnCallback;
m_pContext = pContext;
}
LPTHREAD_START_ROUTINE GetCallback(VOID) const {
return m_pfnCallback;
}
LPVOID GetContext(VOID) const {
return m_pContext;
}
};
//
// CThreadPool - maintains lists of work items, non-IO worker threads and
// IO worker threads
//
class CThreadPool {
private:
//
// private classes
//
//
// CWorkItem - queued app-supplied functions, ordered by priority
//
class CWorkItem : public CPrioritizedListEntry {
public:
FARPROC m_function;
ULONG_PTR m_context;
DWORD_PTR m_tag;
DWORD_PTR m_id;
DWORD m_flags;
HINSTANCE m_hInstModule;
CWorkItem(FARPROC lpfn,
ULONG_PTR context,
LONG priority,
DWORD_PTR tag,
DWORD_PTR * pid,
LPCSTR pszModule,
DWORD flags
) : CPrioritizedListEntry(priority)
{
m_function = lpfn;
m_context = context;
m_tag = tag;
m_id = (DWORD_PTR)0;
m_flags = flags;
if (pszModule && *pszModule)
{
m_hInstModule = LoadLibrary(pszModule);
if (!m_hInstModule)
{
TraceMsg(TF_WARNING, TEXT("CWorkItem::CWorkItem - faild to load %hs (error = %d), worker thread could be abanonded!!"), pszModule, GetLastError());
}
}
else
{
m_hInstModule = NULL;
}
if (pid) {
m_id = (DWORD_PTR)++g_dwWorkItemId;
*pid = m_id;
m_flags |= TPS_ID;
}
}
~CWorkItem()
{
// we used to call FreeLibrary(m_hInstModule) here but we delete the workitem
// when we grab it off of the queue (in RemoveWorkItem). so we have to wait until
// we are actually done running the task before we call FreeLibaray()
}
BOOL Match(DWORD_PTR Tag, BOOL IsTag) {
return IsTag
? ((m_flags & TPS_TAGGEDITEM) && (m_tag == Tag))
: ((m_flags & TPS_ID) && (m_id == Tag));
}
BOOL IsLongExec(VOID) {
return (m_flags & TPS_LONGEXECTIME) ? TRUE : FALSE;
}
};
//
// work item queue variables
//
CPrioritizedList m_queue;
CCriticalSection_NoCtor m_qlock;
HANDLE m_event;
DWORD m_error;
DWORD m_queueSize;
DWORD m_qFactor;
DWORD m_minWorkerThreads;
DWORD m_maxWorkerThreads;
DWORD m_maxQueueDepth;
DWORD m_workerIdleTimeout;
DWORD m_creationDelta;
DWORD m_totalWorkerThreads;
DWORD m_availableWorkerThreads;
#if DBG
DWORD m_queueSizeMax;
DWORD m_qFactorMax;
DWORD m_maxWorkerThreadsCreated;
#endif
//
// private member functions
//
CWorkItem * DequeueWorkItem(VOID) {
CWorkItem * pItem = NULL;
if (!m_queue.IsEmpty()) {
pItem = (CWorkItem *)m_queue.RemoveHead();
--m_queueSize;
}
return pItem;
}
VOID
Worker(
VOID
);
public:
static
VOID
WorkerThread(
VOID
);
BOOL Init(VOID) {
m_queue.Init();
m_qlock.Init();
//
// create auto-reset, initially unsignalled event
//
m_event = CreateEvent(NULL, FALSE, FALSE, NULL);
m_error = (m_event != NULL) ? ERROR_SUCCESS : GetLastError();
m_queueSize = 0;
m_qFactor = 0;
m_minWorkerThreads = MIN_WORKER_THREADS;
m_maxWorkerThreads = MAX_WORKER_THREADS;
m_maxQueueDepth = MAX_QUEUE_DEPTH;
m_workerIdleTimeout = THREAD_IDLE_TIMEOUT;
m_creationDelta = THREAD_CREATION_DAMPING_TIME;
m_totalWorkerThreads = 0;
m_availableWorkerThreads = 0;
#if DBG
m_queueSizeMax = 0;
m_qFactorMax = 0;
m_maxWorkerThreadsCreated = 0;
#endif
return m_error == ERROR_SUCCESS;
}
VOID Terminate(DWORD Limit) {
PurgeWorkItems();
TerminateThreads(Limit);
if (m_event != NULL) {
BOOL bOk = CloseHandle(m_event);
ASSERT(bOk);
m_event = NULL;
}
m_qlock.Terminate();
ASSERT(m_queue.IsEmpty());
//#if DBG
//char buf[256];
//wsprintf(buf,
// "CThreadPool::Terminate(): m_queueSizeMax = %d, m_maxWorkerThreadsCreated = %d, m_qFactorMax = %d\n",
// m_queueSizeMax,
// m_maxWorkerThreadsCreated,
// m_qFactorMax
// );
//OutputDebugString(buf);
//#endif
}
DWORD GetError() const {
return m_error;
}
VOID
SetLimits(
IN DWORD dwMinimumThreads,
IN DWORD dwMaximumThreads,
IN DWORD dwMaximumQueueDepth,
IN DWORD dwThreadIdleTimeout,
IN DWORD dwThreadCreationDelta
)
{
m_minWorkerThreads = dwMinimumThreads;
m_maxWorkerThreads = dwMaximumThreads;
m_maxQueueDepth = dwMaximumQueueDepth;
m_workerIdleTimeout = dwThreadIdleTimeout;
m_creationDelta = dwThreadCreationDelta;
}
VOID MakeAvailable(VOID) {
InterlockedIncrement((LPLONG)&m_availableWorkerThreads);
if (m_qFactor == 0) {
m_qFactor = 1;
} else {
m_qFactor <<= 1;
}
#if DBG
if (m_qFactor > m_qFactorMax) {
m_qFactorMax = m_qFactor;
}
#endif
}
VOID MakeUnavailable(VOID) {
InterlockedDecrement((LPLONG)&m_availableWorkerThreads);
m_qFactor >>= 1;
if ((m_qFactor == 0) && (m_availableWorkerThreads != 0)) {
m_qFactor = 1;
}
}
DWORD
QueueWorkItem(
FARPROC pfnFunction,
ULONG_PTR pContext,
LONG lPriority,
DWORD_PTR dwTag,
DWORD_PTR * pdwId,
LPCSTR pszModule,
DWORD dwFlags
)
{
//
// add a work item to the queue at the appropriate place and create a
// thread to handle it if necessary
//
CWorkItem * pItem = new CWorkItem(pfnFunction,
pContext,
lPriority,
dwTag,
pdwId,
pszModule,
dwFlags
);
if (pItem == NULL) {
return ERROR_NOT_ENOUGH_MEMORY;
}
m_qlock.Acquire();
//
// demand-thread work-items have the highest priority. Put at head of
// queue, else insert based on priority
//
if (dwFlags & TPS_DEMANDTHREAD) {
pItem->InsertHead(&m_queue);
} else {
m_queue.insert(pItem);
}
++m_queueSize;
#if DBG
if (m_queueSize > m_queueSizeMax) {
m_queueSizeMax = m_queueSize;
}
#endif
//
// determine whether we need to create a new thread:
//
// * no available threads
// * work queue growing too fast
// * all available threads about to be taken by long-exec work items
//
BOOL bCreate = FALSE;
DWORD error = ERROR_SUCCESS;
if (m_queueSize > (m_availableWorkerThreads * m_qFactor)) {
bCreate = TRUE;
} else {
DWORD i = 0;
DWORD n = 0;
CWorkItem * pItem = (CWorkItem *)m_queue.Next();
while ((pItem != m_queue.Head()) && (i < m_availableWorkerThreads)) {
if (pItem->IsLongExec()) {
++n;
}
pItem = (CWorkItem *)pItem->Next();
++i;
}
if (n == m_availableWorkerThreads) {
bCreate = TRUE;
}
}
m_qlock.Release();
if (bCreate) {
// if the CreateWorkerThread fails, do NOT pass back an error code to the caller
// since we've already added the workitem to the queue. An error code will
// likely result in the caller freeing the data for the work item. (saml 081799)
CreateWorkerThread();
}
SetEvent(m_event);
return error;
}
DWORD
RemoveWorkItem(
FARPROC * ppfnFunction,
ULONG_PTR * pContext,
HMODULE* hModuleToFree,
DWORD * pdwFlags,
DWORD dwTimeout
)
{
BOOL bFirstTime = TRUE;
DWORD dwWaitTime = dwTimeout;
while (TRUE) {
CWorkItem * pItem;
//
// first test the FIFO state without waiting for the event
//
if (!m_queue.IsEmpty())
{
m_qlock.Acquire();
pItem = DequeueWorkItem();
if (pItem != NULL)
{
if (pItem->m_flags & TPS_LONGEXECTIME)
{
MakeUnavailable();
}
m_qlock.Release();
*ppfnFunction = pItem->m_function;
*pContext = pItem->m_context;
*pdwFlags = pItem->m_flags & ~TPS_RESERVED_FLAGS;
*hModuleToFree = pItem->m_hInstModule;
delete pItem;
return ERROR_SUCCESS;
}
m_qlock.Release();
}
DWORD dwStartTime;
if ((dwTimeout != INFINITE) && bFirstTime) {
dwStartTime = GetTickCount();
}
//
// if dwTimeout is 0 (poll) and we've already waited unsuccessfully
// then we're done: we timed out
//
if ((dwTimeout == 0) && !bFirstTime) {
break;
}
//
// wait alertably: process I/O completions while we wait
//
// FEATURE - we want MsgWaitForMultipleObjectsEx() here, but Win95
// doesn't support it
//
DWORD status = MsgWaitForMultipleObjects(1,
&m_event,
FALSE,
dwWaitTime,
//QS_ALLINPUT
QS_SENDMESSAGE | QS_KEY
);
//
// quit now if thread pool is terminating
//
if (g_bTpsTerminating) {
break;
}
bFirstTime = FALSE;
if ((status == WAIT_OBJECT_0) || (status == WAIT_IO_COMPLETION)) {
//
// we think there is something to remove from the FIFO or I/O
// completed. If we're not waiting forever, update the time to
// wait on the next iteration based on the time we started
//
if (dwTimeout != INFINITE) {
DWORD dwElapsedTime = GetTickCount() - dwStartTime;
if (dwElapsedTime > dwTimeout) {
//
// waited longer than requested. Don't wait again if
// we find there's nothing in the FIFO
//
dwWaitTime = 0;
} else {
//
// amount of time to wait next iteration is amount of
// time until expiration of originally specified period
//
dwWaitTime = dwTimeout - dwElapsedTime;
}
}
continue;
} else if (status == WAIT_OBJECT_0 + 1) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
return WAIT_ABANDONED;
} else {
DispatchMessage(&msg);
}
}
continue;
}
//
// WAIT_TIMEOUT (or WAIT_ABANDONED (?))
//
break;
}
return WAIT_TIMEOUT;
}
DWORD RemoveTagged(DWORD_PTR Tag, BOOL IsTag) {
DWORD count = 0;
m_qlock.Acquire();
CPrioritizedListEntry * pEntry = (CPrioritizedListEntry *)m_queue.Next();
CPrioritizedListEntry * pPrev = (CPrioritizedListEntry *)m_queue.Head();
while (pEntry != m_queue.Head()) {
CWorkItem * pItem = (CWorkItem *)pEntry;
if (pItem->Match(Tag, IsTag)) {
pItem->Remove();
--m_queueSize;
delete pItem;
++count;
if (!IsTag) {
break;
}
} else {
pPrev = pEntry;
}
pEntry = (CPrioritizedListEntry *)pPrev->Next();
}
m_qlock.Release();
return count;
}
DWORD GetQueueSize(VOID) const {
return m_queueSize;
}
VOID PurgeWorkItems(VOID) {
m_qlock.Acquire();
CWorkItem * pItem;
while ((pItem = DequeueWorkItem()) != NULL) {
delete pItem;
}
m_qlock.Release();
}
VOID Signal(VOID) {
if (m_event != NULL) {
SetEvent(m_event);
}
}
DWORD CreateWorkerThread(VOID) {
HANDLE hThread;
DWORD error = ERROR_SUCCESS;
error = StartThread((LPTHREAD_START_ROUTINE)WorkerThread,
&hThread,
FALSE
);
if (error == ERROR_SUCCESS) {
AddWorker();
#if DBG
if (m_totalWorkerThreads > m_maxWorkerThreadsCreated) {
m_maxWorkerThreadsCreated = m_totalWorkerThreads;
}
//char buf[256];
//wsprintf(buf, ">>>> started worker thread. Total = %d/%d. Avail = %d. Factor = %d/%d\n",
// m_totalWorkerThreads,
// m_maxWorkerThreadsCreated,
// m_availableWorkerThreads,
// m_qFactor,
// m_qFactorMax
// );
//OutputDebugString(buf);
#endif
CloseHandle(hThread); // thread handle not required
return ERROR_SUCCESS;
}
return error;
}
VOID TerminateThreads(DWORD Limit) {
while (m_totalWorkerThreads > Limit) {
Signal();
SleepEx(0, FALSE);
}
}
VOID AddWorker(VOID) {
InterlockedIncrement((LPLONG)&m_totalWorkerThreads);
MakeAvailable();
}
VOID RemoveWorker(VOID) {
MakeUnavailable();
InterlockedDecrement((LPLONG)&m_totalWorkerThreads);
}
};