Multithreaded C++: Part 3: RAII And Threads

If boost::threads represent the C of multithreaded programming, then RAII and automatically managed threads represent the C++ of multithreaded programming.

In the last article we promised that using more RAII would allow us to get this code even smaller and better to manage. Here is the result of that:

class threaded_class
{
public:
    threaded_class()
        : m_stoprequested(false),  
          m_thread(boost::bind(&threaded_class::do_work, this)) //Note 2
    {
    }
 
    ~threaded_class()
    {
        m_stoprequested = true;
        m_thread.join(); //Note 2
    }
 
    int get_fibonacci_value(int which)
    {
        boost::mutex::scoped_lock l(m_mutex);
        return m_fibonacci_values.get(which);
    }
 
private:
    volatile bool m_stoprequested;
    std::vector<int> m_fibonacci_values;
    boost::mutex m_mutex;
    boost::thread m_thread;
 
    int fibonacci_number(int num)
    {
        switch(num)
        {
            case 0:
            case 1:
                return 1;
            default:
                return fib(num-2) + fib(num-1);
        };
    }    
 
    // Compute and save fibonacci numbers as fast as possible
    void do_work()
    {
        int iteration = 0;
        while (!m_stoprequested)
        {
            int value = fibonacci_number(iteration);
            boost::mutex::scoped_lock l(m_mutex);
            m_fibonacci_values.push_back(value);
        }
    }                    
};

By using RAII techniques we were able to cut our last example of boost::threads down from 64 lines of code to 52; a 20% savings in code size. Overall we are down by 35% from the original pthreads version.

Notes regarding this version:

Note 1
In this version we initialize the thread during the constructor of the "threaded_class" object. It is critical that the m_thread object is the last object created. We ensure this by declaring it last. Why is this critical? The thread needs to use other objects in the class. By creating it last we make sure that all dependent objects are already created and ready to go by the time the thread is running.
Note 2
During the destructor we join on the thread. We know the thread is the last thing to be created so it is the first thing to be destroyed. By joining on it here we ensure that the thread is stopped running before any dependent object is destroyed.

In proper RAII fashion this class creates and manages all of its own resources, including its thread.

In the next article we will look at making this method of RAII managed threads generic so that we can reuse this technique.

Other articles in this series.

Comments

On note 1 - initialization of m_tread object last

One of the things that called my attention in boost::thread is that it offers a different solution from other object oriented technologies: ACE or Java come to mind now.

In ACE or Java threads, there is a thread object from which you derive and provide implementation of a specific virtual method [svc() in ACE, run() in Java]. One of the dangers of this approach and a common pitfall is that users often commit the error of providing a thread adapter hierarchy that offers solutions for different problems:

class BaseThread : ACE_Base_Task { ... }; // Thread RAII
class PeriodicThread : public BaseThread { ... }; // Adds periodic operations support
class PeriodicSender : public PeriodicThread { ... }; // Real worker

where BaseThread is in charge or RAII (creates the thread in the constructor and the destructor joins the thread), PeriodicThread adds periodic funtionality (as an example) calling a virtual method with a given periodicity. Finally PeriodicSender is the real worker class.

The problem is that during construction ACE_Base_Task is first initialized, then BaseThread, that creates the thread. At this point the thread will be started and it can call virtual methods from PeriodicThread or even PeriodicSender. But this objects are not yet created.

I like boost::thread approach of dividing the thread and the real worker in different classes so that during construction (and launching of the boost::thread class) the worker is already perfectly created. The approach shown in the article somehow contradicts this philosophy by reuniting thread and worker classes. After reading the article, first thing that comes to mind is using a hierarchy as the one shown above to ease the burden of rewriting thread code during construction and destruction.

Just writting a note telling users to initialize the thread as the very last thing in the construction process does not seem too secure a solution.

Oh, I forgot...

... the most important part: nice article!!!