Feedback Search Top Backward
EDM/2

Writing a C++ Thread Class

Written by Gordon W. Zeglinski

 

Introduction

C++ provides an ideal mechanism for encapsulating thread specific variables and other thread creation/maintenance related details. This article will introduce a basic hierarchy which accomplishes these goals.

After reading this article, you should:

  1. Understand how to use the function _beginthread() with C++ code
  2. Understand how to use the thread objects presented here
  3. Be able to extend these basic objects to provide support for thread specific variables.

Using _beginthread

The _beginthread() function is found in one of the following two forms, depending upon which compiler you are using.

For Borland C++ for OS/2:

  int _beginthread(VOID (*start_address)(PVOID),
                   unsigned stack_size,
                   PVOID arglist)
  

And for C-Set++, Watcom, and GCC/EMX:

  int _beginthread(VOID (*start_address)(PVOID),
                   (PVOID)stack,
                   unsigned stack_size,
                   PVOID arglist);
  
The extra parameter stack is specified strictly for backwards compatibility and is not used by the compilers which include it. In general, "start_address" is the address to a function like :

VOID Foo(PVOID Ptr) {
     :
   /* Some body here */
     :
}

What about member functions ?

Let's look at the following class foobar:


class foobar {
   int i;

public:
   foobar();
   VOID ThreadStart(PVOID x);
};
Now suppose that on that one wanted to start a thread using the function ThreadStart() as the starting point. This cannot be done using _beginthread() because of the sometimes forgotten fact that in C++, there is a hidden pointer that is always passed when non-static member functions are called. The actual parameters passed to ThreadStart() are:

foo *thisthe hidden pointer
PVOID xthe declared parameter

In theory, one could use a static member function as the starting function of a thread, however, for some reason C-Set++ doesn't seem to accept this.

C++ Thread Objects

So we've seen the problem when trying to use _beginthread() to start threads on C++ member functions. How do we work around this? The solution is quite simple, we develop a snazzy C++ object hierarchy encapsulate the thread creation process.


                               +--------------+
                               | class Thread |
                               +-+---------+--+
              +------------------|    |    |-----------+
              |                       |                |
              |                       |                |
 +------------+-------------------+   |   +------------+-----------+
 | template< class C,class Arg >  |   |   | template< class C >    |
 | class ClassThreadwArg          |   |   | class ClassThreadwoArg |
 +--------------------------------+   |   +------------------------+
                                      |
                                      |
                      +---------------+--------------+
                      | template< class C>           |
                      | class StaticClassThreadwoArg |
                      +------------------------------+

Figure 1. Snazzy Thread Hierarchy

Let's now look at how this hierarchy fits together by examining the classes in it.

Thread


class Thread {
protected:
   APIRET Error;
   TID tid;
   ULONG StackSize;
   USHORT Started    :1;
   USHORT Suspended  :1;
   USHORT            :(sizeof(USHORT)-2);

   VOID SetThreadID(TID t){tid=t;}

public:
   Thread(ULONG StackS=4096);

   TID GetThreadID() {return tid;}
   APIRET GetError() {return Error;}

   VOID ChangeStackSize(ULONG S) {
     if (!Started) StackSize=S;
   }

   virtual VOID Run(VOID)=0;
   VOID Start();
   VOID SuspendThread();
   VOID KillThread();
   VOID ResumeThread();

friend VOID ThreadStarter(PVOID);
};
The Thread class is an abstract class that forms the basis of this hierarchy. It contains the basic functions for manipulating threads. The advantage of using an abstract class is twofold: first, it groups all of the thread manipulation functions into one class; and secondly, it provides an easy method for the end user to extend the usefulness of the hierarchy.

But how does Thread work?

The function friend VOID ThreadStarter(PVOID) should look familiar from our discussion of the _beginthread() function. It has the exact form of the function that is required by the _beginthread() function and is declared as a friend so that it can have access to the members of the Thread class. Ideally, a static member function would be used, but do to some compiler quirks, a friend is used instead. No big deal - the result is the same.

Using it is quite simple. To start a thread, one calls the member function Start(), which then calls _beginthread() and uses ThreadStarter() as the thread's starting point. ThreadStarter() in turn calls the member function Run(), which has yet to be defined. The parameter thread starter takes is declared as PVOID, but it really is a Thread * pointer. Thus, the instance of Thread is known by ThreadStarter().

ClassThreadwoArg


template<class C>class ClassThreadwoArg:public Thread{
   C * TheClass;
   VOID (C::* TheFunc)(VOID);

public:
   ClassThreadwoArg(C * tc,VOID (C::* tf)(VOID)): Thread() {
      TheClass=tc;
      TheFunc=tf;
   }

   ClassThreadwoArg(VOID (C::* tf)(VOID)): Thread() {
      TheFunc=tf;
   }

   ClassThreadwoArg():Thread() {
      TheClass=NULL;
      TheFunc=NULL;
   }

   VOID SetInstance(C * tc) { TheClass=tc; }
   VOID SetFunc(VOID (C::* tf)(VOID)) { TheFunc=tf; }

   VOID Run(){
      ((TheClass)->*(TheFunc))();
   }
};
The ClassThreadwoArg class is the most commonly used class. It starts a thread on a member function which takes no arguments. Note that the function Run now has a body, the beauty of which is its simplicity. However, its simplicity hides the the power of this class. This class can be used to start threads in any class's instance using any member function (taking no arguments) as a starting point. Also, it is completely type-safe.

ClassThreadwArg


template<class C,class Arg>class ClassThreadwArg:public Thread{
   C * TheClass;
   VOID (C::* TheFunc)(Arg*);
   Arg *TheArg;

public:
   ClassThreadwArg(C * tc,VOID (C::* tf)(Arg*), Arg* ta): Thread() {
      TheClass=tc;
      TheFunc=tf;
      TheArg=ta;
   }

   ClassThreadwArg(): Thread() {
      TheClass=NULL;
      TheFunc=NULL;
      TheArg=NULL;
   }

   VOID SetInstance(C* tc){ TheClass=tc; }
   VOID SetFunc(VOID (C::* tf)(Arg*)) { TheFunc=tf; }
   VOID SetArg(Arg* ta) { TheArg=ta; }
   VOID Run() {
      (TheClass)->*(TheFunc))((TheArg));
   }
};
The only difference between this class and ClassThreadwoArg, is that this class allows the possibility of passing an pointer argument to the started member function.

The last class will not be discussed in depth because it is basically the same as ClassThreadwoArg but it is for static member functions.

Overview

Figure 2 shows the flow of function calls as a new thread is created.

Thread 1Thread 2
Start -> _beginthread->ThreadStarter -> Run -> (Some Member function)

Figure 2. Function flow when creating a thread.

Using the C++ Threading Objects

Now, let's take a look at how the threading objects can be used to implement thread specific variables. Keeping with tradition, let's suppose we want to create a bunch of foo threads. One could create the following class:


class foo {
   Thread *TheThread;
        :
      /* thread-specific variables */
        :
public:
   foo( /* list of thread-specific initial values */ ) {
      TheThread=new ClassThreadwoArg<foo>(this,
                                          &foo::ThreadEntry);
           :
         /* do some initialization */
           :
   }
   VOID ThreadEntry();
   VOID StartThread(){TheThread->Start();}
        :
      /* Add whatever other functions are needed */
        :
};
To create a new foo thread, one would create an instance of a foo object and then call the StartThread() member function. The following code shows this process.

INT main(VOID) {
   foo **FooThreads;

   FooThreads=new foo[2];
   if (FooThreads==NULL)
      exit(1);

   FooThreads[0]=new foo(/* some thread specific info */);
   FooThreads[1]=new foo(/* some more thread specific info */);
   FooThreads[0]->StartThread();
   FooThreads[1]->StartThread();

     :
   /* do something */
     :

   return 0;
}

Summary

This brings us to the end of our C++ threading journey. Although, the objects presented here are missing some bells and whistles, adding them should be relatively easy. The reason _beginthread() cannot be directly used has been discussed and a method to get around this limitation is presented. The sample foo class illustrated how these thread objects can be used to develop thread specific variables.

As usual, question and comments are welcome.