Active Object, a C++ CookBook for Easier Threading

Home    KjellKod Code Page
 
 [Download]

Source code and google test example can be downloaded from GitHub

git://github.com/KjellKod/active-object.git

Or you can use the possibly old snapshot below

<Note: 2011-11-08>

Check out my updated Active Object with C++11, it is cross platform code thanks to just::thread implementation of the C++ standard library. The code is yours to download and use of course  :)
In the zip/tgz files above and the GitHub link you have access to the C++11 as well as this code


Introduction

Herb Sutter described at the Effective Concurrency 2009 seminar how active objects worked. I was intrigued since I realized this was a great help compared to juggling raw therads. 

Then in July 2010 he followed up with a
blog [1] and article [2]. In the article he describes both doing it with todays C++ and also how to do it with C++0x and function objects. 

Since I didn't have access to C++0x in 2009 and was I inspired by his seminar I decided to write my own Active object with the same easy to use API but more template magic to get it done and fake the C++0x goodies. The original KjellKod::Active object was influenced by my previous signal-n-slot [3] work, but in this text I describe an (August 2010) updated version.

After being re-inspired by the Herb's article [2] 
as well as his Gotw83: Generic callbacs [4]. I made the decision to to update the KjellKod::Active object and also put it in writing. My example is done with pthreads but changing pthreads for win32 threads is easy and I might add that later on. 


Disclaimer

  1. In the source code example I use my home rolled, proof-of-concept, and quite silly CircularFifo for a queue but I've made it very easy in the active.h/cpp files to change this. Preferably the CircularFifo should be replaced for a dynamic sized queue that does not require any external synchronization by the caller (why not a std::queue protected internally by a mutex). If you want to use this active object I suggest you also look at my Active Object C++11 and use the shared queue from that one instead,.
    The reason for using the CircularFifo is just that I can :) and of course that since I wrote about it once I don't mind showing it again (even though it's more a proof of concept than intended for use)

  2. To show my example I use a setup with CMake + google test. I'm thinking of using just "raw" c++ but this is how it's made now. Either way you can easily roll your own thing using just Active.h/cpp as your cookbook :)

  3. Yes my example is very silly as well. It's just integers baked into a struct and moved around and stored. In real life you probably would have the jobs similar to Herb's example printing or saving data to a file or some other slow I/O stuff, but I think this is enough to show how it works.

  4. Apart from my silly queue you should replace the auto_ptr for some other smart pointer that doesn't have the limitations of dear old auto_ptr

  5. This is a bare bone example. I've not tried to have any "optimization" and ignored concepts such as work stealing between other Active objects (which would easily mess with the FIFO order) or return values back to callers or any other intra-thread communication scheme outside the basic topic of Active Object.


Active Object

Below you find the Active class. The createActive() is a factory help function that will create an instance of Active and initiate its internal thread. 

I'm skipping over, modifying and simplifying some parts of the code here, but just check the source code for all the exact & correct details if you need to.


  class Active {
  private:  
    
    msg_queue<std::auto_ptr<Callback> > mq; 
// the queue w/ internal synchronization
    pthread_t thd;
    bool done;
    
    Active(); 
    Active(const Active&);
    void operator=(const Active&);

    void doDone(){done = true;} 
    void run();            
    static void* runThread(void* args); // required due to thread API


    public:
      virtual ~Active();     
      void send(std::auto_ptr<Callback> msg_);
    
     static std::auto_ptr<Active> createActive();
// Factory & thread start
};


In the implementation the destructor uses generic callbacks and binds this to a message
that is pushed onto the queue. Once the message is executed (last) the thread will exit. This is a sure way of emptying the queue and executing all its work before finishing.

Active::~Active() {  
   send(bind(this, &Active::doDone));
   pthread_join(thd, NULL); 
}

Pushing jobs onto the queue. Each "outside caller" uses this API as the only means of affecting what the thread works with. The thread keeps working until the end message is received (sent by the destructor) 

void Active::send(std::auto_ptr<Callback> msg_) {
   mq.push(msg_); // Add asynchronously to work queue
}

void Active::run() {
  while (!done) {    
    if (mq.isEmpty()) {
       pthread_yield();  // help switching if on same core
    } else {
       std::auto_ptr<Callback> msg;
       mq.pop(msg); 
       (*msg)();
    }
  }
}


Just like
ksignals [3] but heavily influenced after reading [4]
 I use  stored generic callbacks which can have one or none arguments. Using some helper functions gives then very easy to use syntax for working with the Active objects.

Backgrounder & Generic Callbacks


class Backgrounder {
 private:    
   std::auto_ptr<Active> active;     // the active object :)
   void bgSave(const std::auto_ptr<Data> msg_); // will be executed by the thread

 public:
   Backgrounder()
:active(Active::createActive()){}
   virtual ~Backgrounder(){}
   void saveData(
std::auto_ptr<Data> msg_); // Asynchronous msg API     
 };

The saveData function will create and push asynchronously a job message onto the queue. As soon as the job is on the queue the function returns and the caller can continue with other stuff. In the meantime the jobb will in due time be processed by the background thread, finally saving the data in bgSave(...) without slowing down the original caller. 

void Backgrounder::saveData(std::auto_ptr<Data> msg_) {
  active->send(bind(this, &Backgrounder::bgSave, msg));
}

Finally it can be worth looking at the simple generic callbacks and how they are encapsuled (it's pretty much the same setup as in Gotw83 [4]). A baseclass, Callback, helps to store the callbacks and to execute them witout exposing the template arguments to the Active or to the message queue.

  struct Callback {
    virtual void operator()() = 0;
    virtual ~Callback(){};
  };

Using the () operator aids in simplifying the code. Just showing the zero parameter callback example (see source code for the parameter callback) gives:

  template<typename T>
  class ActiveCallback<T, void>: public Callback {
   private:
    typedef void (T::*Func)(); 
     T*    object;
     Func func;  

   public:
     ActiveCallback ( T* obj_, Func f_): object(obj_), func(f_) {} 
         ~ActiveCallback() {}
         void operator()() { (object->*func)(); } 
      };


Helper function: bind()


Using the helper function bind(..) gives easy to use syntax that helps in write and understand any code that uses the Active concept  
// i.e. 
active->send(bind(this, &Backgrounder::bgSave, msg)) 

   template<typename T>
   std::auto_ptr<Callback> bind (T* obj_, void (T::*f_)(void)) {
      Callback* func = new ActiveCallback<T, void>(obj_,f_);
      return std::auto_ptr<Callback> (func);
   }
 

References


[2] Herb Sutter, Dr.Dobbs article "Prefer Using Active Objects Instead of Naked Threads"
[3] Kjell Hedström, KSignals "Signals and Slots"   ;)
[4] Herb Sutter, Gotw 83: Style Case Study #2: Generic Callbacks

Feedback

As always, it is a good thing to get feedback, you can do this through the corresponding Active Object entry on my  blog 

ċ
KjellKod-active-object-c689ff9.tar.gz
(2394k)
Kjell Hedström,
Nov 8, 2011, 1:05 AM
ċ
KjellKod-active-object-c689ff9.zip
(2416k)
Kjell Hedström,
Nov 8, 2011, 1:05 AM
ċ
active.cpp
(2k)
Kjell Hedström,
Nov 8, 2011, 1:02 AM
ċ
active.h
(3k)
Kjell Hedström,
Nov 8, 2011, 1:01 AM
ċ
activecallback.h
(3k)
Kjell Hedström,
Nov 8, 2011, 1:02 AM
Comments