Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Effective Threads in C++ - Part 1: The Basic Thread Class

3.95/5 (11 votes)
22 Nov 20064 min read 1   2K  
Wrapping the Win32 Thread API into a C++-friendly class.

Introduction

Over the past few years, multithreaded apps have become the mainstay of my development repertoire. I've even found myself writing multithreaded apps where it wasn't needed. Worker threads allow Win32 applications to improve the user experience and increase performance.

For C++ programs and programmers, the Windows API's C interface doesn't necessarily fit well into new or existing class hierarchies. However, the thread concept has become the mainstay of parallel programming practice, and has become more appropriate in a wider and wider variety of circumstances.

In part 1 of this three article series, I will present my general thread class, explaining the design decisions behind it and how to use it to get the greatest effect.

The Thread Class

Before we get ahead of ourselves, let's take a look at the public interface of the Thread class:

class Thread
{
public:
    Thread();
    virtual ~Thread();

    // Suspend - Suspends the thread (if one is active)
    void Suspend();

    // Resume - Resumes a previously suspended thread
    void Resume();

    // Terminate - Terminates the thread (if one is active).
    // Prefer another means of exiting the thread, as
    // calling Terminate() does not allow the thread to free
    // any resources it may hold
    void Terminate();

    // IsThreadActive - Called in the context of another
    // (external) thread to test whether the thread is
    // currently running
    bool IsThreadActive() const;
};

This rather basic interface is also rather clean. It exposes a fair amount of the operations the Win32 API allows on threads themselves, in a clean, C++-friendly class.

Now, you may be saying to yourself, "Hey? You said it was a complete interface. There isn't even a way to create a thread in there!". Ahh, we're getting there. But before we do, we have to go through template land.

That’s right, my implementation of the Thread class uses a little template-magic. This magic is needed to allow us to use the same code among different client classes. After all, you wouldn't want to re-write the class for each client, would you?

So, let's look a little closer at a few particular lines of the source code.

template<class T, class P>
class Thread
{
public:
    typedef void (T::*ThreadFunc)( P );
    
    // ...
    // Same as above
    // ...
};

There are actually two different template parameters: T and P; or, put another way: Class and Parameter.

To invoke a method on an object, the compiler needs two things: the object's type (the T in the template argument list), and the method's signature (which is composed of the return type, class, method name, and parameter list). For this implementation, the method is assumed to return nothing (void) and takes one parameter (see article #3 in this series for a more general solution which doesn't make these assumptions).

The typedef makes things very easy. It is a method typedef, specifying that ThreadFunc is a function pointer to a function of T which takes a P as a parameter and returns nothing. See why the typedef makes it easier?

Now that we've pushed and shoved our way through template land, let's look at the meat of the Thread class: the Run() method.

// Run - Start the Thread and run the method
// pClass->(*pfFunc), passing p as an argument.
// Returns true if the thread was created 
// successfully, false otherwise
bool Run( T* pClass, ThreadFunc pfFunc, P p );

That's it! That's the public interface for the Thread class. Nothing too intense. And the implementation is not much more complex. So, with that, how do we use this new class?

Using the Thread Class

Let's look at an example on how to use the Thread class:

class TestClass
{
    ...

private:
    Lib::Thread<TestClass, int> m_thread;
};

In this example, the Thread class should be used as a data member of another class (with a little modification, the class can be used as a stand-alone object; I will leave that as the dreaded and hated exercise to the reader............ for now). The enclosing class is the first template parameter to the Thread class. This is needed for the compiler to invoke the correct method of the correct class. The second template parameter is the type of argument that will be passed to the thread method.

To start the thread, call the Thread::Run() method:

m_thread.Run( this, &TestClass::DoWork, i );

This will create a new thread using the standard Win32 ::CreateThread(...) method. This new thread will immediately turn around and call the method specified to the Thread::Run() method (I know it may not look like it, but we're specifying a function in all those arguments).

The three arguments to the Thread::Run() method are:

  • The object we will be invoking the method upon
  • The method we will be invoking
  • The parameter we will pass to the method

What do we need to specify a function call? From the compiler's perspective, they are return type, class, method name, and parameter list. From the runtime's perspective, we also need an object. Since the return type is hard coded, and the class and parameter list is specified in the template argument, all the compiler needs is a method name, and the runtime needs an object. Guess what? Those are the first two parameters of the Thread::Run() method (parameter 2 and 1, respectively).

After the thread is called, it (essentially) turns around and calls:

this->DoWork( i );

As soon as this method exits, the thread is finished with its work, and it exits as well.

Conclusion

The Thread class is rather basic, but very useful. It provides a C++ wrapper for the powerful Win32 C Threading API. This particular implementation makes a few assumptions: the Thread class must be a member of an enclosing class, the return type must be void, and the method takes a single parameter.

These assumptions may be less than ideal for the time, so article #3 will show you how to get around these assumptions using more template magic. (Trust me, it won't be that painful.)

But first, article #2 will show you how to extend the Thread class into the most useful threading paradigm I have ever come across.

History

  • 2006.11.21 - First revision.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here