Object-Oriented Threads / by Daniel Ehrman

Over the weekend, I decided to throw together a C++ class to simplify the user's (programmer's) interface with pthreads. The pthreads library is great in a C environment but exposes too much complexity for my liking in an object-oriented program. What I wanted was a way to treat an object—or more specifically, an object's member function—like a thread without having to create and join the threads myself. The end result would be, say a vector of objects of a common class, all executing in parallel and returning their result without (visible) barriers. (And yes, this basically amounts to a watered-down version of the Python Thread class.)

Here's what I came up with:


template<class return_type> class Thread
{
protected:
  // Virtual function to be specified by the user
  virtual return_type thread_function() = 0;

public:
  // Start the thread.
  bool start_thread()
  {
    return !pthread_create(&internal_thread, NULL, start_thread, this);
  }

  // Get the return value from the thread.
  return_type thread_return()
  {
    pthread_join(internal_thread, NULL);
    return return_value;
  }

private:
  // Actual function passed to pthread_create()
  static void *start_thread(void *obj_ptr)
  {
    // Execute the user-specified virtual function and store its return value for later use.
    ((thread<return_type> *)obj_ptr)->return_value = \
      ((thread<return_type> *)obj_ptr)->thread_function();
    return NULL;
  }

  return_type return_value;
  pthread_t internal_thread;
};

To use the class, you inherit it in the definition of the class that you want to make "threadable" like so:

class TestClass: public Thread<int>
{
public:
  virtual int thread_function() { return some_function(); }
  int some_function() { return arg0 * arg1; }
  int arg0;
  int arg1;
};

Just as you would with Python's Thread class, here you need to specify which member function should execute on a thread: the simplest way to do this is to have the "thread_function" return the function of your choice. The "thread_function" is what the internal Thread functions will look for, so it's important to define it here. (Otherwise, you won't be threading anything.)

So in the end a sample usage of these threaded objects might look like this:

int results[NUM_THREADS];
TestClass some_objects[NUM_THREADS];

for (int i=0; i < NUM_THREADS; i++)
  {
    // Initialize the object.
    some_objects[i].arg0 = i;
    some_objects[i].arg1 = i + 1;

    // Start the thread.
    if(!some_objects[i].start_thread())
      {
        cout << "ERROR: Could not create thread " << i << endl;
        return 1;
      }
  }

// Get the results of the threads.
for (int i=0; i < NUM_THREADS; i++)
  {
    results[i * i] = some_objects[i].thread_return();
  }

Sure, this is a largely useless block of code, but it demonstrates the point: there's no need to think about thread IDs, mutexes, or barriers here. Just initialize the object, kick it off, and collect the results when it comes back.

It's pretty simple, and you'll likely find many other solutions like this online. But things got interesting when I decided to test this out with some sample programs—more on the performance characteristics of these threaded objects in my next post.…