Re: Thread Pre-Emption Question . . .



Here's my critsect.h header with the reader/writer class, this is a long time tested engineered class uses in a hign-end multi-million $ product line. You can trust it is reliable. I have no problem sharing it. :-)

//*******************************************************************
// (c) Copyright 1999 Santronics Software, Inc. All Rights Reserved.
//*******************************************************************
// File Name : critsect.h
// Created : 4/15/98
// Programmer: Hector Santos, SSI
//*******************************************************************

#ifndef __CRITSECT_H
#define __CRITSECT_H

class TCriticalSection {
public:
TCriticalSection();
~TCriticalSection();
void Enter();
void Leave();
BOOL Check() const;
int Count() const;
class Lock {
public:
Lock(TCriticalSection &cs): CS(cs) { CS.Enter(); }
~Lock() { CS.Leave(); }
private:
TCriticalSection &CS;
};
private:
CRITICAL_SECTION cs;
int InCount;
TCriticalSection(const TCriticalSection &);
};

inline TCriticalSection::TCriticalSection()
{
InitializeCriticalSection(&cs);
InCount = 0;
}

inline TCriticalSection::~TCriticalSection()
{
DeleteCriticalSection(&cs);
}

inline void TCriticalSection::Enter()
{
EnterCriticalSection(&cs);
InCount++;
}

inline void TCriticalSection::Leave()
{
InCount--;
LeaveCriticalSection(&cs);
}

inline BOOL TCriticalSection::Check() const
{
return InCount > 0;
}

inline int TCriticalSection::Count() const
{
return InCount;
}

typedef TCriticalSection::Lock TCriticalSectionGrabber;

class TReaderWriter {
public:
TReaderWriter();
~TReaderWriter();
void ReaderEnter();
void ReaderLeave();
void WriterEnter();
void WriterLeave();
BOOL ReadOk() const { return Readers >= 0 || WriterRecursion > 0; }
BOOL WriteOk() const { return WriterMutex.Check(); }
long GetReaders() const { return Readers; }
long GetWriters() const { return WriterRecursion; }
private:
long Readers;
long WriterRecursion;
HANDLE ReadingOk;
HANDLE ReadingActiveSemaphore;
TCriticalSection WriterMutex;
TReaderWriter(const TReaderWriter &) {}
};

inline TReaderWriter::TReaderWriter()
{
Readers = -1;
ReadingOk = CreateEvent(NULL, TRUE, TRUE, NULL);
ReadingActiveSemaphore = CreateSemaphore(NULL, 1, 1, NULL);
WriterRecursion = 0;
}

inline TReaderWriter::~TReaderWriter()
{
CloseHandle(ReadingOk);
CloseHandle(ReadingActiveSemaphore);
}

inline void TReaderWriter::ReaderEnter()
{
TCriticalSectionGrabber grab(WriterMutex);
WaitForSingleObject(ReadingOk, INFINITE);
if (InterlockedIncrement(&Readers) == 0) {
WaitForSingleObject(ReadingActiveSemaphore, INFINITE);
}
}

inline void TReaderWriter::ReaderLeave()
{
if (InterlockedDecrement(&Readers) < 0) {
ReleaseSemaphore(ReadingActiveSemaphore, 1, NULL);
}
}

inline void TReaderWriter::WriterEnter()
{
WriterMutex.Enter();
WriterRecursion++;
ResetEvent(ReadingOk);
if (WriterRecursion == 1) {
WaitForSingleObject(ReadingActiveSemaphore, INFINITE);
}
}

inline void TReaderWriter::WriterLeave()
{
WriterRecursion--;
if (WriterRecursion == 0) {
SetEvent(ReadingOk);
ReleaseSemaphore(ReadingActiveSemaphore, 1, NULL);
}
WriterMutex.Leave();
}

class TReaderGrabber {
public:
TReaderGrabber(TReaderWriter &rw): RW(rw) { RW.ReaderEnter(); }
~TReaderGrabber() { RW.ReaderLeave(); }
private:
TReaderWriter &RW;
};

class TWriterGrabber {
public:
TWriterGrabber(TReaderWriter &rw): RW(rw) { RW.WriterEnter(); }
~TWriterGrabber() { RW.WriterLeave(); }
private:
TReaderWriter &RW;
};

#endif

Pops wrote:
Ok, here's your solution to explore.

Use the Workthread (Call it the Signal Thread) to queue your events/data for other worker background threads to handle (Call these the DATA threads). This will allow the Signal thread to work as possible with NO processing overhead.

This is very easier to do with a FIFO queue list with reader/writer locks.

The Signal Thread can signal the data thread(s) or use a signal data thread to sit on a poll to watch the FIFO queue list count.

Doing it this way, the data thread will not be pressured on working at 40ms.

If your testing shows the queue is growing too fast, then you add additional data threads. :-)

I recently did this myself to resolve the missed file I/O events in ReadDirectoryChangeW() because the thread handler was too slow to process the file events coming in. I was extremely surprise and tickled pink on how efficient and reliable RDCW() is using this event queing method.

Using a queue class is easy but make sure you reader/writer logic lock to it.

Here is an outline:

typedef struct tagTMyEventData {
... your data ...
} TMyEventData;

class CMyQueue : public CList< TMyEventData, TMyEventData>
{
public:
// Add element to queue
void Add( const TMyEventData &o )
{
TWriterGrabber grab(Mutex);
AddHead( o );
}
// Get first element off queue
BOOL Get(TMyEventData &o)
{
TReaderGrabber grab(Mutex);
if (IsEmpty()) return FALSE;
o = RemoveTail();
return TRUE;
}

private:
TReaderWriter Mutex;
};

If you can't find a TReaderWriter class, dime a dozen, I will give you ours.

Then in your Signal Worker Thread:

CMyQueue eventQ; // GLOBAL

UINT WorkerThread(LPVOID pParam)
{
while(1) {
if (waitForEvent() == ABORT) break;
TMyEventData d = {0};
... fill in event data ...
eventQ.Add(d);
}
return 0;
}

and your Data Thread:

UINT DataThread(LPVOID pParam)
{
while(1) {
if (waitForEvent() == ABORT) break;
Sleep(15);
TMyEventData d;
while (eventQ.Get(d)) {
... process d ....
}
}
return 0;
}

See how that works! <g>

--
HLS

Rob wrote:
Hi Guys,

Thanks for your quick responses.

'Windows isn't a RTOS - so you may be fighting an uphill battle!'

I'm aware of that but we have very tight control of the aplications and services running on the machine which is good enough for our purposes 99.9% of the time.

I've had a look at the GetThreadTimes API but the resolution is not really good enough, I believe it's arround 15ms which isn't particulary helpful in this case.

Regarding the MM timers, we're using a PCI hardware device to signal the timer events which is locked to a reference signal (this is a broadcast application).

I'd also like to avoid having to set the priority of the thread too high because the processing involoves quite a lot of network I/O, so I need to make sure the kernel and network services get enough CPU time.

Does anyone know of a way I can check the context switch count for the thread? If I could get the count before and after the processing it would be useful to log with other timing information when the processing takes too long.

Cheers
Rob.
.



Relevant Pages