Click to See Complete Forum and Search --> : Worker Thread Class?


WhorlyWhelk
July 30th, 2007, 05:00 AM
I have been trying to make this.

Here is what I have come up with. Can you give ideas to make this better or point out problems? It is my first attempt and I've only tried it with a trivial program so far.

Thanks for any help!

workerthread.h:
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H

using namespace fastdelegate;

class WorkerThread {
private:
bool created,quit;
unsigned int workerID;
HANDLE h,hEvent,wMutex;
FastDelegate0<BOOL> funclist[6]; // ptrs for any BOOL fn()
int fd;
public:
WorkerThread(); // initialize
~WorkerThread(); // destructor
BOOL CreateWorker(); // begin thread
HANDLE GetHandle(){ return h; } // get thread handle
BOOL AddWork(BOOL (*func)()); // add function
void Quit(); // terminate thread
friend unsigned __stdcall threadEntry(void*);
};

#endif

workerthread.cpp:
#include <windows.h>
#include <process.h>
#include "FastDelegate.h"
#include "workerthread.h"

using namespace fastdelegate;

WorkerThread::WorkerThread(){
created=false;
quit=false;
fd=0;
h=NULL;
hEvent=CreateEvent(NULL,true,false,NULL);
wMutex=CreateMutex(NULL,false,NULL);
}

WorkerThread::~WorkerThread(){
WaitForSingleObject(h,INFINITE);
CloseHandle(h);
}

BOOL WorkerThread::CreateWorker(){
if (created)
return FALSE;
h=(HANDLE)_beginthreadex(NULL,0,&threadEntry,(void*)this,0,&workerID);
if (h==NULL)
return FALSE;
created=true;
return TRUE;
}

BOOL WorkerThread::AddWork(BOOL (*func)()){
WaitForSingleObject(wMutex,INFINITE);
if (fd>5){
ReleaseMutex(wMutex);
return FALSE;
}
funclist[fd].bind(func);
fd++;
ReleaseMutex(wMutex);
SetEvent(hEvent);
return TRUE;
}

void WorkerThread::Quit(){
WaitForSingleObject(wMutex,INFINITE);
quit=true;
ReleaseMutex(wMutex);
SetEvent(hEvent);
}

unsigned __stdcall threadEntry(void* arg){
WorkerThread* w=(WorkerThread*)arg;
while (1){
WaitForSingleObject(w->hEvent,INFINITE);
WaitForSingleObject(w->wMutex,INFINITE);
if (w->quit){
ReleaseMutex(w->wMutex);
break;
}
FastDelegate0<BOOL> func(w->funclist[w->fd-1]);
//w->funclist[w->fd-1]();
w->fd--;
if (w->fd==0)
ResetEvent(w->hEvent);
ReleaseMutex(w->wMutex);
(func)();
}
CloseHandle(w->hEvent);
}

NMTop40
July 30th, 2007, 08:16 AM
How I do it is similar but you don't see the declaration of the actual thread function.

I have 4 classes, Thread (which might also be called ThreadFunc), ThreadFactory and ThreadGenerator and ThreadPool. ThreadFunc and ThreadFactory are both abstract. ThreadGenerator and ThreadPool are concrete though.

- Thread has a virtual method run()
- ThreadFactory has a method create() that creates an instance of a Thread object (but does not actually create a new thread).
- ThreadGenerator actually creates the new thread. You pass in a reference to a factory and it creates the Thread object and creates a thread which calls its run() method.
- ThreadPool is a place to store the Thread objects as well as to have some control over them.

The actual function that creates a thread will throw an exception if the thread failed to create.

I also have a template class called ConsumerThread that derives from Thread. This implements run() for you but you have to derive process(). It reads an item off a queue and processes it using process(). Your items on the queue could derive from an abstract class calls Task or Job and you perform it with its run() method or whatever its method is called. Actually I do have such a wrapper for this model. I think my Thread class may even derive from Task (both have a virtual run() method).

JVene
July 31st, 2007, 03:11 PM
WhorlyWhelk, good show! My own thread classes are among the most used and valued in my collection. You'll find your own efforts well worth it to you in the years ahead (I started mine when OS/2 was new).

Are you focused on portable code? I ask because Windows has the critical section object which functions in the way you're using the mutex here (but is faster/lighter), and both are valid. Linux (so far as I've followed) either doesn't have the critical section, or it's a recent addition. If you're making portable code here (heck, even if your not), I highly suggest you consider making a lock object.

Once you get threads launched, you will frequently make use of locks (on a mutex or critical section depending on the OS). The simple call to WaitForSingleObject and the required ReleaseMutex form a pair that might not survive exceptions well (that is, you might never release the lock).

Unless, of course, you wrap the concept of a lock into an object.

This has the additional benefit of creating a means of coding that can mutate to critical sections under Windows, mutexes under Linux, and optionally mutexes under Windows if that's the best choice.

Consider:



void somefunc()
{
MyLock l( sync );

////.... stuff here under lock
}





sync is an instance of an object (I call them Syncs) which represent the critical section or the mutex. The MyLock instance l represents a lock on that mutex. The constructor and destructor are inline, so it evaporates to efficient code. The constructor obtains the lock on the mutex or critical section, as appropriate - blocks, of course, if the lock isn't available - and, when it falls out of scope (even under an exception), the lock is released (and this is the best part) every time.

It might sound like a simple object pair, and it is - but RAII used like this eliminates the origin of bugs that are just maddening.