Multithreading Without Synchronization: Boost C++ ASIO Strand

When it comes to multithreaded programming, we usually start thinking of using various thread synchronization mechanisms explicitly, including use of mutexs, semaphores and conditions, etc., to control memory visibility of shared resources to multiple threads to avoid data race. For example, Boost C++ provides such mechanisms for us in its Boost.Thread.

However, in my opinion, we may be better off writing multithreaded programs for our problems without explicitly using  these mechanisms, if and whenever possible. As we all know,  thread synchronization comes at the cost of performance (context switches), debugging and programming complexity.

In this blog, I am going to use Boost C++ ASIO Strand to implement a pair of producer and consumer working on a shared integer vector container : the producer puts “num” of integers ranging from 1 to num=10 (in that order) into the container and the consumer removes them one by one from it simultaneously (thus multithreading). The producer and consumer wait one second before adding or removing each integer from the container, respectively. The one second emulates time duration that some asynchronous I/O operation may take to complete. You may change its value to see different output. Furthermore, the vector container is shared between the producer and the consumer. However, in the code, we do not have to worry about the synchronization issue, as I would explain later on. To understand the code, I assume readers are already familiar with C++ class, constructor, destructor, Boost.ASIO, Boost.Bind (function object), boost::asio::deadline_timer and the callback handler concept in asynchronous I/O operations.

I am going to implement a producer_consumer class and show a pool of threads executing its callback handlers “producer” and “consumer” to add and remove an integer to and from a shared integer vector container simultaneously using Boost ASIO Strand, but without using explicit thread synchronization mechanisms. The following is the class declaration in “timerStrand.h” file:

Code list 1 for “timerStrand.h”
#ifndef TIMER_STRAND_H
#define TIMER_STRAND_H
#include <asio.hpp>

class producer_consumer
{
public:
  producer_consumer(boost::asio::io_service& ios); // ctor
   void producer(); // producer callback handler
   void consumer(); // consumer callback handler
  ~producer_consumer(); // dtor
private:
  boost::asio::strand strand_;
  boost::asio::deadline_timer timer1_;
  boost::asio::deadline_timer timer2_;
   int count_; // data member shared between producer and consumer
   std::vector intvec_; // data structure shared between producer and consumer
  enum { num = 10 }; // maximum number of integers added to the container
};// std::cout is not thread-safe; it is implicitly shared between producer and consumer

#endif

The constructor (ctor) of the class takes a reference to a boost::asio::io_service object as the argument (line 8). It is needed to initialize data members timer1_ (line 14) and timer2_ (line 15), as it would become clear in the class implementation.

The class stores data members int count_ (line 16) and an integer vector container intvec_ (line 17). These data members are shared and modified from the callback handlers “producer” method (line 9) and the “consumer” method (line 10).

In addition, I want to point out that std::cout stream is not thread-safe: its output strings would become unpredictable when both the producer and the consumer callback handlers are using them simultaneously. However, due to the unique mechanism of Boost ASIO Stand, this is not the case in the program: the shared std::cout streams become thread safe without explicit thread synchronization, too. You can see it when you run the program.

The destructor (dtor) (line 11) is to verify that each of the integers added into the vector container by the producer has been consumed by the consumer (no resource leakage) when the producer_consumer object is destroyed. The class-specific enum “num = 10″ (line 18) specifies the number of integers that the producer should add to the shared vector container for the consumer to consume. I chose to “encapsulate” it inside our class, rather than making it a global variable in current compiling file (“timerStrand.h”) by declaring “const int numb = 10″ right after the include directory statements. You may change it to 2000 to play with the code and see different output.

The implementation of the class is shown in the following “timerStrand.cpp”:

Code list 2 for “timerStrand.cpp”
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include "timerStrand.h"

using std::cout;
using std::endl;

producer_consumer::producer_consumer(boost::asio::io_service& ios)
  : strand_(ios),
  timer1_(ios, boost::posix_time::seconds(1) ),
  timer2_(ios, boost::posix_time::seconds(1) )
{
   count_ = 0;
   timer1_.async_wait(
   strand_.wrap(
   boost::bind(&producer_consumer::producer, this)));   // starts producer
}

void producer_consumer::producer()
{
  if ( count_ < num)
  {
     ++count_;
     intvec_.push_back(count_);
    std::cout << count_ <
     " pushed back into integer vector." << std::endl;
     timer1_.async_wait(strand_.wrap(
         boost::bind(&producer_consumer::producer, this))); // loops back
     timer2_.async_wait(strand_.wrap(
         boost::bind(&producer_consumer::consumer, this))); // start consumer
  }
}
void producer_consumer::consumer()
{
  if (( count_ <= num ) &&
   ( !intvec_.empty()) ) // ios may have no work to do when the vector is empty
  {
    std::cout << intvec_.back()
     << " in the integer vector is consumed " << endl;
     intvec_.pop_back();  // remove the element
     timer2_.async_wait(strand_.wrap(
         boost::bind(&producer_consumer::consumer, this)));  // loops back
  }
}

producer_consumer::~producer_consumer()
{
  std::cout << "At the end, size of the vector container is: " // verify that every
    << intvec_.size() << std::endl;    // integer in the vector has been consumed
}

In the ctor, we initialize the data member strand_ with the ios passed in (line 10), so does the timer1_ and timer2_ data members (lines 11 and 12). These two timers would time out in one second emulating some asynchronous I/O operation taking one second to complete. You may change them to different values to see how the output of the code would change when the asynchronous I/O operation takes different time to complete. Inside the initialization body, we set the initial value of count_ to zero (line 14).

Here comes the meat of the code on lines 15, 16 and 17: when timer1_ times out, the producer_consumer::producer() method will be called. We wrap the function object returned from the boost::bind() (line 17) with strand_.wrap() function. This identical member function wrap() of a single, identical strand_ object is used in lines 28, 30 and 42, too! By wrapping function objects with an identical Boost Strand object (the strand_ data member in our code), multiple calls to asynchronous I/O handlers (the “producer” and “consumer” methods in our code) from different threads are guaranteed to complete one before the other one can start. This is guranteed irrespective of the number of threads that are calling io_service::run(). It is how Boost::ASIO Strand synchronizes callback handlers manipulating shared resources in multithreaded programs without explicit thread synchronization on the part of the programmer.

The logic of the remaining code in the class implementation is straightforward: the producer start adding integers to the vector container. After each integer is added, it calls consumer to consume the integer (lines 30 and 31). After removing one integer from the container, the consumer loops back to see if it has another integer to remove (lines 42 and 43). Please note that the consumer can not start until there is an integer to consume in the vector container. So the condition on line 37 is added.

Lastly, use of our producer_consumer class is shown in the following main.cpp file:

Code list 3 for “main.cpp”
#include <boost/thread.hpp>
#include "timerStrand.h"

int main()
{
  boost::asio::io_service ios;
    producer_consumer pc(ios);
    boost::thread t(boost::bind(
   &boost::asio::io_service::run, &ios)); // run producer,consumer in new thread
    ios.run();       // run producer,consumer in current (main) thread
    t.join(); 
    return 0;
}

In the main(), we call boost::asio::io_service::run() method both in a new thread (lines 8 and 9) and in current (main) thread (line 10). Since the run() method is called from different thread, a pool of  2 threads are now executing the handlers concurrently to do the work.

The above code was tested with MSVC++ 2008 professional on Windows XP professional with Boost 1_44. It should be able to compile and run on other platforms where Boost C++ library is installed.

EDIT:  The title of this blog seems to be misleading, isn’t it? I as a programmer do not have to worry about the thread synchronization, but the library writers of Boost ASIO have done that for me. Well, if you were writing multithreaded programs sitting back assured of no thread synchronization issues would bother you, isn’t it very comfortable? In case that you may still not be convinced of it, in my next blog, I am going to show you a Boost.Thread C++ multithreaded program which uses no explicit thread synchronization mechanisms at all.

About these ads
This entry was posted in C++ Multithreading (3). Bookmark the permalink.

One Response to Multithreading Without Synchronization: Boost C++ ASIO Strand

  1. Marat Abrarov says:

    I’m a fan of C++ and Boost.Asio (and all other Boost C+ Libraries, but Asio especcially).
    I try to show that Boost.Asio (as one of the best implementors of proactor pattern) can do much more then it’s written in asio’s docs.
    Look for my project at: http://sourceforge.net/projects/asio-samples.
    It’s active object pattern implemented by means of Boost.Asio (only) with asio custom memory allocation support. It really works but I’m just alone in my interest.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s