Creating a C++ Thread Class

Introduction

Recently, my brother asked me whether there was an easy way to create a C++ class that facilitated object orientated threading. I have written many multi-threaded libraries in the past; however, they are all in C. C has always been my language of choice for low-level programming; I use C++ for GUI development. Although there are many excellent examples of object-orientated threading on CodeGuru, none of the classes introduced suited all of my brother's needs and my curiosities. He wanted a thread class that had the following attributes:

  • It supports both event driven and interval based asynchronous threading.
  • It supports both homogeneous and specialized threading.
  • It provides a FCFS (First Come First Serve) stack-queue for posting and handling multiple tasks.
  • It is portable.
  • It is simple to implement.

To support the new class, CThread, other supporting classes were also developed. These classes included the CMutexClass, CEventClass, and CTask classes. The CMutexClass and CEventClass provide resource management while the CTask class is a base class for deriving classes that support homogeneous asynchronous threading.

What Is Threading?

Every process has at least one thread of control and every process can perform at least one task at a time. A process that has more than one thread of control defines a multi-threaded process. A multi-threaded process allows multiple tasks to run asynchronously from within the environment of the process.

Resource Management—Thread Synchronization

Because threads within a multi-threaded process share the same resources, OS level control mechanisms are necessary to insure data integrity. A loss of data integrity occurs when one thread is modifying a variable while another thread is attempting to read it or two threads are attempting to modify the same variable at the same time. To prevent this scenario, the OS provides a Mutual Exclusion Object known in short as a mutex. In multi-threaded applications, mutexes, deployed programmatically, prevent multiple threads from accessing a single resource at the same time. When a thread needs access to a resource, it must first acquire a mutex. Once a thread has acquired a mutex, other threads attempting to acquire the same mutex are blocked and placed in a low-CPU usage wait state. Once a thread has completed data access, it releases the corresponding mutex; this allows other threads to acquire it and access the corresponding data.

Poor implementations of mutexes can result in resource starvation, also known as deadlock. Resource starvation occurs when one or more threads are competing for the same resource. Deadlock can also occur if a thread attempts to acquire a mutex twice.

Example

Thread A Thread B
Acquires mutex(1) to modify data item 1 Acquires mutex(2) to modify data item 2
Wants mutex(2) to view data item 2 Wants mutex(1) to view data item 1

Deadlock occurs in the example above because Thread A is blocked trying to acquire mutex(2), which is held by thread B. Thread B is blocked trying to acquire mutex(1) which is blocked by Thread A.

Like mutexes, condition variables, in UNIX, are another form of synchronization mechanism. Condition variables allow threads to rendezvous. They allow one thread to notify another that a change has occurred. In Windows, these are events.

Operating System Calls

The following table is a list of the various functions used to implement threading in the CMutexClass, CEventClass, CTask, and CThread classes.

Function OS Description Class Used in
CreateThread Windows Creates a Windows thread CThread
pthread_create UNIX - POSIX THREADS Creates a UNIX thread CThread
pthread_join UNIX - POSIX THREADS Waits for a UNIX thread to terminate CThread
pthread_attr_init UNIX - POSIX THREADS Sets a thread attribute structure to default CThread
pthread_attr_setstacksize UNIX - POSIX THREADS Sets the stack size value of the thread attribute structure CThread
WaitForSingleObject Windows Waits for an object to be signaled CThread, CMutexClass, CEventClass
CreateMutex Windows Creates a named or unnamed mutex CMutexClass
CloseHandle Windows Releases resources alloacate to a Windows handle CMutexClass, CEventClass, CThread
ReleaseMutex Windows Releases a prevously acquired mutex locked by WaitForSingleObject CMutexClass, CEventClass
pthread_mutexattr_init UNIX - POSIX THREADS Initializes a mutex attribute structure CMutexClass, CEventClass
pthread_mutex_init UNIX - POSIX THREADS Initializes a mutex using a provided attribute structure CMutexClass, CEventClass
pthread_mutex_lock UNIX - POSIX THREADS Locks a mutex CMutexClass, CEventClass
pthread_mutex_unlock UNIX - POSIX THREADS Unlocks a mutex previously locked by pthread_mutex_lock CMutexClass, CEventClass
pthread_mutex_destroy UNIX - POSIX THREADS Releases resources allocated to a mutex CMutexClass, CEventClass
CreateEvent Windows Creates a Windows event object CEventClass
SetEvent Windows Sets a Windows event object to signaled CEventClass
pthread_cond_signal UNIX - POSIX THREADS Unblocks a thread blocked on pthread_cond_wait CEventClass
pthread_cond_wait UNIX - POSIX THREADS Blocks on a condition variable CEventClass
pthread_cond_init UNIX - POSIX THREADS Initializes a condition variable CEventClass

Creating a C++ Thread Class

The CMutexClass Class

The CMutexClass class encapsulates the system level mutex functions and a mutex synchronization object. Mutex creation occurs during object instantiation with the mutex created as unblocked. The class provides two member functions Lock and Unlock. The Lock member function locks a mutex assigning it to the calling thread. The mutex remains locked to the calling thread until the calling thread releases it using the Unlock member function. Threads that attempt to acquire a locked mutex by calling the Lock member function are blocked, placed into a low CPU consumption wait state until the blocking thread releases the mutex.

CMutexClass::CMutexClass(void)
:m_bCreated(TRUE)
{
#ifdef WINDOWS
   m_mutex = CreateMutex(NULL,FALSE,NULL);
   if( !m_mutex ) m_bCreated = FALSE;
#else
   pthread_mutexattr_t mattr;

   pthread_mutexattr_init( &mattr );
   pthread_mutex_init(&m_mutex,&mattr);

#endif
   memset(&m_owner,0,sizeof(ThreadId_t));

}

CMutexClass::~CMutexClass(void)
{
#ifdef WINDOWS
   WaitForSingleObject(m_mutex,INFINITE);
   CloseHandle(m_mutex);
#else
   pthread_mutex_lock(&m_mutex);
   pthread_mutex_unlock(&m_mutex);
   pthread_mutex_destroy(&m_mutex);
#endif
}

/**
 *
 * Lock
 * the same thread cannot lock the same mutex
 * more than once
 *
 **/
void
CMutexClass::Lock()
{
   ThreadId_t id = CThread::ThreadId();
   try {
      if(CThread::ThreadIdsEqual(&m_owner,&id) )
         // the mutex is already locked by this thread
         throw "\n\tthe same thread can not acquire a mutex twice!\n";
#ifdef WINDOWS
      WaitForSingleObject(m_mutex,INFINITE);
#else
      pthread_mutex_lock(&m_mutex);
#endif
      m_owner = CThread::ThreadId();
   }
   catch( char *psz )
   {
#ifdef WINDOWS
      MessageBoxA(NULL,&psz[2],"Fatal exception CMutexClass::Lock",
                  MB_ICONHAND);
      exit(-1);
#else
      cerr << "Fatal exception CMutexClass::Lock : " << psz;
#endif


   }

}

/**
 *
 * Unlock
 * releases a mutex.  Only the thread that acquires
 * the mutex can release it.
 *
 **/
void 
CMutexClass::Unlock()
{
   ThreadId_t id = CThread::ThreadId();
   try
   {
      if( ! CThread::ThreadIdsEqual(&id,&m_owner) )
      throw "\n\tonly the thread that acquires a mutex can
             release it!";

      memset(&m_owner,0,sizeof(ThreadId_t));
#ifdef WINDOWS
      ReleaseMutex(m_mutex);
#else
      pthread_mutex_unlock(&m_mutex);
#endif
   }
   catch ( char *psz)
   {
#ifdef WINDOWS
      MessageBoxA(NULL,&psz[2],"Fatal exception CMutexClass::Unlock",
                  MB_ICONHAND);
      exit(-1);
#else
      cerr << "Fatal exception CMutexClass::Unlock : " << psz;
#endif

   }
}

Member Functions

Function Description
void CMutexClass() Constructor
void Lock() Locks mutex object or wait if blocked
void Unlock() Unlocks/unblocks a previously blocked mutex

Example

int g_iStorage = 0;
CMutexClass MyMutex;

void StoreValue( int *pInt )
{
   MyMutex.Lock();           //the gate keeper. only one thread
                             //allowed in at a time

      g_iStorage = *pInt;    //protected data, critical code section

   MyMutex.Unlock();         //unblocks, allowing another thread to
                             //access g_iStorage
}

The CEventClass Class

The CEventClass class encapsulates the Windows event functions, a Windows event object, UNIX condition variable functions, and a UNIX condition variable. The functions encorporated into the CEventClass class are SetEvent and CreateEvent under Windows, and phtread_cond_init, pthread_cond_destroy, pthread_cond_signal, and pthread_cond_wait under UNIX. Event synchronization objects are called condition variables under UNIX but for the purpose of simplification, I will refer to both condition variables and event objects as event objects.

#include "Thread.h"


#include <iostream>
using namespace std;

CEventClass::CEventClass(void)
:m_bCreated(TRUE)
{
   memset(&m_owner,0,sizeof(ThreadId_t));
#ifdef WINDOWS
   m_event = CreateEvent(NULL,FALSE,FALSE,NULL);
   if( !m_event )
   {
      m_bCreated = FALSE;
   }
#else
   pthread_mutexattr_t mattr;

   pthread_mutexattr_init(&mattr);
   pthread_mutex_init(&m_lock,&mattr);
   pthread_cond_init(&m_ready,NULL);

#endif
}

CEventClass::~CEventClass(void)
{
#ifdef WINDOWS
   CloseHandle(m_event);
#else
   pthread_cond_destroy(&m_ready);
   pthread_mutex_destroy(&m_lock);
#endif
}


/**
 *
 * Set
 * set an event to signaled
 *
 **/
void
CEventClass::Set()
{
#ifdef WINDOWS
   SetEvent(m_event);
#else
   pthread_cond_signal(&m_ready);
#endif
}

/**
 *
 * Wait
 * wait for an event -- wait for an event object
 * to be set to signaled.  Must be paired with a
 * call to reset within the same thread.
 *
 **/
BOOL
CEventClass::Wait()
{

   try
   {
      ThreadId_t id = CThread::ThreadId();
      if( CThread::ThreadIdsEqual(&id,&m_owner) )
      {
         throw "\n\tinvalid Wait call, Wait can not be called more
                than once"
            "\n\twithout a corresponding call to Reset!\n";
      }
      ThreadId_t zero;
      memset(&zero,0,sizeof(ThreadId_t));

      if( memcmp(&zero,&m_owner,sizeof(ThreadId_t)) != 0 )
      {
         throw "\n\tanother thread is already waiting on this event!\n";
      }

      m_owner = CThread::ThreadId();
#ifdef WINDOWS
      if( WaitForSingleObject(m_event,INFINITE) != WAIT_OBJECT_0 )
      {
         return FALSE;
      }
#else
      pthread_mutex_lock(&m_lock);
      pthread_cond_wait(&m_ready,&m_lock);
      return TRUE;
#endif
   }
   catch( char *psz )
   {
#ifdef WINDOWS
      MessageBoxA(NULL,&psz[2],"Fatal exception CEventClass::Wait",
                  MB_ICONHAND);
      exit(-1);
#else
      cerr << "Fatal exception CEventClass::Wait: " << psz;
#endif

   }
   return TRUE;
}

/**
 *
 * Reset
 * reset an event flag to unsignaled
 * wait must be paired with reset within the
 * same thread.
 *
 **/
void
CEventClass::Reset()
{
   try
   {
      ThreadId_t id = CThread::ThreadId();
      if( !CThread::ThreadIdsEqual(&id,&m_owner) )
      {
         throw "\n\tunbalanced call to Reset, Reset must be called
                from\n"
            "\n\tthe same Wait-Reset pair!\n";
      }

      memset(&m_owner,0,sizeof(ThreadId_t));

#ifndef WINDOWS
      pthread_mutex_unlock(&m_lock);
#endif
   }
   catch( char *psz )
   {
#ifdef WINDOWS
      MessageBoxA(NULL,&psz[2],"Fatal exception CEventClass::Reset",
                  MB_ICONHAND);
      exit(-1);
#else
      cerr << "Fatal exception CEventClass::Reset: " << psz;
#endif

   }
}

Member Functions

Function Description
void Set() Sets an event state to signaled, notifying blocked thread
BOOL Wait() Places the calling thread in a blocked state until the event state is set to signaled. Returns TRUE on success, FALSE on failure.
void Reset() Resets a signaled event to unsignaled

Example of an event object being used by a recieving thread:

CEventClass event;
   .
   .
//thread code
   .
   .
   while(bContinueRunning)
   {

      event.Wait();     // wait for an event to occur

      // perform some task
       .
       .
      event.Reset();    // reset the event to un-signaled
   }
   .
   .

Example of an event object used by one thread signaling another:

CEventClass event;
   .
   .
// change some data
   .
   .
   event.Set();    // notify thread that an event has occurred,
                   // set event to signaled
   .
   .

Creating a C++ Thread Class

The CTask Class and Non-Specialized Threads

In many of the thread programming examples that I have seen, data for thread processing is stored in a global variable, protected by a mutex. Instructions for operating on the data are integrated into the thread function. I define this form of threading as Specialized Asynchronous Threading (SAT). Ideally, the data and the corresponding functionality for processing the data should be encapsulated into the same object. I define this form of threading as Homogeneous Asynchronous Threading (HAT). Under HAT, threads are not specialized. For example, there would not be a printing thread and an I/O thread in a HAT solution. Instead, a single thread could perform both types of tasks because the tasks are implemented as complete objects; that is, they contain both the data and the functionality necessary to process the data. The CTask class is a base class that facilitates HAT-based threading.

typedef enum {
   TaskStatusNotSubmitted,
   TaskStatusWaitingOnQueue,
   TaskStatusBeingProcessed,
   TaskStatusCompleted } TaskStatus_t;

class CTask
{
private:
   CMutexClass m_mutex;
   TaskStatus_t m_state;
   ThreadId_t m_dwThread;
public:
   void SetTaskStatus(TaskStatus_t state)
   {
      m_mutex.Lock();
         m_state=state;
      m_mutex.Unlock();
   }

   void SetId(ThreadId_t *pid)
   {
      memcpy(&m_dwThread,pid,sizeof(ThreadId_t));
   }

   /**
    *
    * Wait
    * waits for up to timeoutSeconds for a task
    * to complete
    *
   **/
   BOOL Wait(int timeoutSeconds)
   {
      timeoutSeconds = timeoutSeconds * 1000;
      if( Status() != TaskStatusCompleted &&
          timeoutSeconds > 0 )
      {
         Sleep(100);
         timeoutSeconds = timeoutSeconds - 100;
      }
      if( Status() == TaskStatusCompleted ) return TRUE;
      return FALSE;
   }

   /**
    *
    * Where
    * returns current state of a task
    *
    **/
   TaskStatus_t Status()
   {
      TaskStatus_t state ;

      m_mutex.Lock();
        state = m_state;
      m_mutex.Unlock();
      return state;
   }

   void Thread(ThreadId_t *pId)
   {
      memcpy(pId,&m_dwThread,sizeof(ThreadId_t));
   }

   CTask(){m_state=TaskStatusNotSubmitted;
           memset(&m_dwThread,sizeof(ThreadId_t),0); }
   ~CTask(){}
   virtual BOOL Task()=0;
};

Member Functions

Function Description
m_mutex Mutex object synchronization object
virtual BOOL Task() Called by a CThread object to perform the task
TaskStatus_t Status() Determines the tasks status: TaskStatusNotSubmitted, TaskStatusWaitingOnQueue, TaskStatusBeingProcessed, or TaskStatusCompleted
void Thread(ThreadId_t *pid) Returns the thread ID of the processing thread
BOOL Wait(int iTimeInSeconds) Places a calling thread into a wait state until the task completes or iTimeInSeconds elapses. If a task does not complete within iTimeInSeconds, FALSE is returned; otherwise, TRUE is returned

I have not defined the CThread class; however, its definition is not necessary to understand how it interacts with a CTask object. The list below presents an outline of how the two object types interact.

The procedure for processing a CTask object

  • A CTask object is passed to a CThread object to be processed.
  • The CThread object places the CTask object in a First Come First Served Queue.
  • The CThread object sets the CTask object's state to TaskStatusWaitingOnQueue.
  • The CThread object pops the CTask object off of the wait queue.
  • The CThread object changes the CTask object's state to TaskStatusBeingProcessed.
  • The CThread object calls CTask object's member function "task" to perform the task.
  • The CThread object changes the CTask object's state to TaskStateCompleted.

The CThread Class, Putting It All Together

Member Functions

Function Description
void CThread() Constructor initializes object data and starts the thread.
void ~CThread() Terminates thread if it is running and frees resources.
BOOL Event(LPVOID lpvData) Places a data block on the event stack/queue and notifies the object's thread that data is waiting to be processed.
BOOL Event(CTask *pTask) Places a CTask object on the event stack/queue and notifies the object's thread that a task is waiting to be performed.
int GetEventsPending() Returns the number of events waiting on the event stack.
ThreadId_t GetId() Returns the object's thread ID.
DWORD GetErrorFlags() Returns the object's error flags. If there are no errors, a value of 0 is returned (NO_ERRORS). If there are errors, one or more of the following flags will be set: MUTEX_CREATION (a mutex object could not be created), EVENT_CREATION (an event object could not be created), THREAD_CREATION (the object's thread could not be created), ILLEGAL_USE_OF_EVENT (the Event member function was called for a interval based thread).
BOOL PingThread(DWORD dwTimeoutMilli) Determines whether the object's thread is running. Returns TRUE if the thread is running, FALSE if it is not. Timeout is in seconds.
SetPriority(DWORD dwPriority) Sets thread's priority, Windows only.
BOOL Start() Starts the object's thread.
BOOL Stop() Stops the object's thread.
void SetIdle(DWORD dwIdle) Changes a thread's idle time in milliseconds, used with interval-based threading.
SetThreadType(ThreadType_t typ,DWORD dwIdle) Changes the thread type between ThreadTypeEventDriven and ThreadTypeIntervalDriven.
m_mutex A mutex object used for synchronization, see CMutexClass.
ThreadState_t ThreadState() Returns the state of a thread: ThreadStateBusy (the thread is processing an event), ThreadStateWaiting (the thread is waiting for a new event), ThreadStateDown (the thread is not running), ThreadStateShutingDown (the thread is in the process of shutting down).

Now that you have learned the supporting classes, it's time to look at the main class, the CThread class—the workhorse. The CThread class supports two types of threads, Event Driven and Interval Driven. An Event Driven thread is a thread that remains in a wait state, blocked on an event object, until the event object's state changes from unsignaled to signaled. A new event occurs when a different thread places a task in a CThread object's queue and notifies the object's thread by setting its event object to signaled. Once signaled, the thread wakes up and pops tasks from its event queue until the queue is empty.

The CThread object invokes the OnTask member function for each task. Tasks are processed in a First Come First Serve (FCFS) order. Hence, the first task placed in a CThread object's queue is processed first, followed by the second and so on. A mutex object synchronizes queue access, allowing additional events to be placed on the queue while the thread is processing older ones. Once the queue is empty, the thread resets the event object to unsignaled and returns to waiting for an event object. The CThread class supports two types of Event Driven threads: specialized in unspecialized threads, see CTask.

Creating a C++ Thread Class

To implement a specialized thread, a new class must be derived from the CThread class. The derived class should contain a redefined implementation of OnTask to process the object's data types.

Example

#include "Thread.h"
class CIncrementThread : public CThread
{
public:
   int counter;

   virtual BOOL OnTask( LPVOID lpv )
   {
      ThreadId_t id;

      GetId(&id);
      if( lpv )
      {
         int *pInt = (int *)lpv;

         //don't use cout here, output could be broken up due to
         //threading
         printf("\tthread(%ld, counter+%d=%d, counter incremented\n",
                id,*pInt,(counter+=*pInt));
      }
      return TRUE;
   }

   virtual BOOL OnTask()
   {
      ThreadId_t id;

      GetId(&id);
      //don't use cout here, output could be broken up due to
      //threading
      m_mutex.Lock();    // protect the counter variable
         printf("\tthread(%ld, counter++= %d, counter incremented)\n",
                id,(++counter));
      m_mutex.Unlock();


      return TRUE;
   }

      int GetValue()
      {
         int counterValue = 0;
         m_mutex.Lock();    // protect the counter variable
            counterValue = counter;
         m_mutex.Unlock();
         return counter;
      }

      void Reset()
      {
         m_mutex.Lock();
             counter = 0;
          m_mutex.Unlock();
      }

   CIncrementThread(){counter=0;}
   ~CIncrementThread(){}
};

int main( int argc,
          char *argv[])
{
   // object allocated and thread started
   CIncrementThread MyThread;
   int two=2;

   while( MyThread.GetValue() < 20 )
   {
      MyThread.Event();    // increment value by one
      Sleep(100);          // pauses the root thread for 100
                           // milliseconds
   }

   MyThread.Reset();
   while( MyThread.GetValue() < 40 )
   {
      MyThread.Event(&two);
      Sleep(100);
   }
}


OUTPUT:
        thread(5220, counter++= 1, counter incremented)
        thread(5220, counter++= 2, counter incremented)
        thread(5220, counter++= 3, counter incremented)
        thread(5220, counter++= 4, counter incremented)
        thread(5220, counter++= 5, counter incremented)
        thread(5220, counter++= 6, counter incremented)
        thread(5220, counter++= 7, counter incremented)
        thread(5220, counter++= 8, counter incremented)
        thread(5220, counter++= 9, counter incremented)
        thread(5220, counter++= 10, counter incremented)
        thread(5220, counter++= 11, counter incremented)
        thread(5220, counter++= 12, counter incremented)
        thread(5220, counter++= 13, counter incremented)
        thread(5220, counter++= 14, counter incremented)
        thread(5220, counter++= 15, counter incremented)
        thread(5220, counter++= 16, counter incremented)
        thread(5220, counter++= 17, counter incremented)
        thread(5220, counter++= 18, counter incremented)
        thread(5220, counter++= 19, counter incremented)
        thread(5220, counter++= 20, counter incremented)
        thread(5220, counter+2=2, counter incremented
        thread(5220, counter+2=4, counter incremented
        thread(5220, counter+2=6, counter incremented
        thread(5220, counter+2=8, counter incremented
        thread(5220, counter+2=10, counter incremented
        thread(5220, counter+2=12, counter incremented
        thread(5220, counter+2=14, counter incremented
        thread(5220, counter+2=16, counter incremented
        thread(5220, counter+2=18, counter incremented
        thread(5220, counter+2=20, counter incremented
        thread(5220, counter+2=22, counter incremented
        thread(5220, counter+2=24, counter incremented
        thread(5220, counter+2=26, counter incremented
        thread(5220, counter+2=28, counter incremented
        thread(5220, counter+2=30, counter incremented
        thread(5220, counter+2=32, counter incremented
        thread(5220, counter+2=34, counter incremented
        thread(5220, counter+2=36, counter incremented
        thread(5220, counter+2=38, counter incremented
        thread(5220, counter+2=40, counter incremented

In the example above, I have derived a CIncrementThread class from a CThread class. In the class definition, I redefined both the OnTask() and OnTask(LPVOID) virtual member functions. In the OnTask() implementation, I add one to the objects counter variable. The other OnTask member function takes a pointer to an integer value, and adds the pointers value to the counter member variable. This example illustrates the two types of events that a thread can process. Because the counter variable has the potential of being accessed by more than one thread, I use the CThread::m_mutex object to insure that it is only accessed by one thread.

HAT (Homogeneous Asynchronous Threading) threads are implemented using both the CThread and the CTask classes.

Example

#include "Thread.h"
class CTaskIncrementer: public CTask
{
private:
   int counter;
   int incr;
public:
   void SetIncr(int iValue) 
   {
      m_mutex.Lock();
         incr = iValue;
      m_mutex.Unlock();
   }

   int GetIncrementValue()
   {
      int incrValue;
      m_mutex.Lock();
         incrValue=incr;
      m_mutex.Unlock();
         return incrValue;
   }

   int GetValue()
   {
      int counterValue = 0;
      m_mutex.Lock();    // protect the counter variable
         counterValue = counter;
      m_mutex.Unlock();
         return counter;
   }

   BOOL Task()
   {
      ThreadId_t id;

      Thread(&id);

      m_mutex.Lock();
         printf("\tthread(%ld, counter+%d=%d, counter incremented\n",
                id,incr,(counter+=incr));
      m_mutex.Unlock();
         return TRUE;
   }
   CTaskIncrementer(){counter=0;}
   ~CTaskIncrementer(){}
};

int
main(int argc,
   char *argv[])
{
   CTaskIncrementer incr;
   CThread thr;

   incr.SetIncr(2);
   while( incr.GetValue() < 40 ) thr.Event(&incr);
}

OUTPUT:
       thread(5700, counter+2=2, counter incremented
       thread(5700, counter+2=4, counter incremented
       thread(5700, counter+2=6, counter incremented
       thread(5700, counter+2=8, counter incremented
       thread(5700, counter+2=10, counter incremented
       thread(5700, counter+2=12, counter incremented
       thread(5700, counter+2=14, counter incremented
       thread(5700, counter+2=16, counter incremented
       thread(5700, counter+2=18, counter incremented
       thread(5700, counter+2=20, counter incremented
       thread(5700, counter+2=22, counter incremented
       thread(5700, counter+2=24, counter incremented
       thread(5700, counter+2=26, counter incremented
       thread(5700, counter+2=28, counter incremented
       thread(5700, counter+2=30, counter incremented
       thread(5700, counter+2=32, counter incremented
       thread(5700, counter+2=34, counter incremented
       thread(5700, counter+2=36, counter incremented
       thread(5700, counter+2=38, counter incremented
       thread(5700, counter+2=40, counter incremented

Creating a C++ Thread Class

An Interval Driven thread is a thread that wakes up at predefined intervals, checks to see whether there is a change in the environment, processes the changes in the environment, sleeps for the next interval, and then wakes up and does it all over again. To implement a interval driven thread, you derive a CThread class that redefines OnTask(LPVOID). Once the thread has been instantiated, you call the SetThreadType member function with the parameter ThreadTypeIntervalDriven and an interval in milliseconds.

Example

#include "Thread.h"

class CIncrementThread : public CThread
{
public:
   int counter;

   virtual BOOL OnTask()
   {
      ThreadId_t id;

      GetId(&id);
      //don't use cout here, output could be broken up due to
      //threading
      m_mutex.Lock();    // protect the counter variable
         printf("\tthread(%ld, counter++= %d, counter incremented)\n",
                id,(++counter));
      m_mutex.Unlock();


      return TRUE;
   }

   int GetValue()
   {
      int counterValue = 0;
      m_mutex.Lock();    // protect the counter variable
         counterValue = counter;
      m_mutex.Unlock();
      return counter;
   }

   void Reset()
        {
            m_mutex.Lock();
               counter = 0;
            m_mutex.Unlock();
        }

   CIncrementThread(){counter=0;}
   ~CIncrementThread(){}
};

int
main( int argc, 
    char *argv[] )
{
   CIncrementThread thr;

   thr->SetThreadType(ThreadTypeIntervalDriven,100);
   Sleep(500);

}

OUTPUT:
        thread(6104, counter++= 12, counter incremented)
        thread(6104, counter++= 13, counter incremented)
        thread(6104, counter++= 14, counter incremented)
        thread(6104, counter++= 15, counter incremented)
        thread(6104, counter++= 16, counter incremented)

Conclusion

There you have it, a full function thread object. I have tested on Linux and the classes work fine. I have yet to test on SunOS or any of the other UNIX platforms that these classes should support. When compiling on Windows, be sure to specify /Mt or /Mtd for code generation; this identifies your application as a multi-threaded application. For Linux, the following make file works:

CC=g++
LIBS=-lpthread -lrt
CFLAGS=-DSUNOS -DNANO_SECOND_SLEEP

OBJS=Thread.cpp EventClass.cpp MutexClass.cpp main.cpp


EXECS = thread

all: $(EXECS)

thread: $(OBJS)
    $(CC) $(CFLAGS) -o thread $(OBJS) $(LIBS)


clean:; rm -f *.o $(EXECS)


About the Author

Walter Capers

Currently I am a Software Architect for Compuware specializing in software security. Languages: C/C++, FORTRAN, COBOL, Java. Libraries: OpenGL, MFC, WIN32. Platform Experience: AIX, HP-UX, SunOS, Open VMS, OSF, AS400, AIX, SGI, Linux, Windows CE, Windows. I have a strong background in 3D graphics programming, TCP/IP development, threading, cross platform development, encryption and secured communications, and system level programming. In the early years before OpenGL made its way to the PC world, I authored one of the first high performance double buffered 3D graphics engines that supported both DOS and Windows providing real time 3D graphics for Engineering Technology Associate's Finite Element Model Builder, FEMB. You can see their products at www.eta.com. I also hold a US patent: http://www.patentstorm.us/patents/6894690.html

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Hundreds of millions of users have adopted public cloud storage solutions to satisfy their Private Online File Sharing and Collaboration (OFS) needs. With new headlines on cloud privacy issues appearing almost daily, the need to explore private alternatives has never been stronger. Join ESG Senior Analyst Terri McClure and Connected Data in this on-demand webinar to take a look at the business drivers behind OFS adoption, how organizations can benefit from on-premise deployments, and emerging private OFS …

Most Popular Programming Stories

More for Developers

RSS Feeds