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

Creating a Work Queue (Thread Pool) Application Using Boost.Asio, Boost.Thread, and Boost.Application

4.75/5 (7 votes)
23 Nov 2013CPOL4 min read 57.9K   625  
This article present a Boost.Application, Boost.Asio, and Boost.Thread library used to build a Work Queue application.

Important Note:<o:p>  

This article presents the old (0.3) version of Boost.Application (Library Proposal to boost.org)!<o:p>

 A new version (0.4) with other interface is already available to download on: 

https://github.com/retf/Boost.Application<o:p>

Note that version 0.3 is no longer maintained! Version 0.4, is now maintained and receives regular updates.  

Introduction

This article presents how to use Boost.Thread and Boost.Asio with Boost.Application to build a Work Queue (Threadpool) application. Note that Boost.Application is not yet an official Boost C++ library.

A “Boost.Application” provides an application environment, or start point to any people that want a basic infrastructure to build a system application on Windows or Unix Variants (e.g. Linux, MacOS).

This article is a continuation of the article: http://www.codeproject.com/Articles/662221/Create-a-Windows-Service-Application-Using-the-Boo

Feedback

If you are boost user, and use Boost Mailing Lists, please provide your feedback about the library, directly on list. (Specify if you think if the library should be accepted as part of boost.com). http://www.boost.org/community/groups.html

If you are a CodeProject user, please provide your feedback about library direct in this page.

Bugs

If you find any BUG, please, send it to me at: re.tf@acm.org

1. Basic Setup

Please refer topics 1 (Boost.Application Installation) and 4 (Create Application Skeleton on Visual Studio) of http://www.codeproject.com/Articles/662221/Create-a-Windows-Service-Application-Using-the-Boo to know how get your environment ready to use.

2. Boost.Application Introduction

Boost.Application supports basically two flavors of applications that can be:

  • Common Application: This kind of application is a usual Interactive Terminal/Console Application.
  • Server Application: This kind of application generates a Service (Windows), or a background process/Daemon (Unix).

This article is focused on “Common Application”. The “Common Application” type will be used to pack a thread pool builds on top of Boost.Asio and Boost.Thread.

Check library manual to know more about other features provided in Boost.Application, the manual can be accessed on: boost_installation\libs\application\doc\html\index.html.

The documentation is in alpha status. If you are a native English speaker and would like to contribute to the documentation, or want contribute with some new functionality for libraries (see future work on documentation), please contact me.

3. Sample Application Project

This article provides a how to tutorial that shows how to use Boost.Application library to build a “common application” type; this article cover in less detail level others boost library’s like:

  • Boost.Thread;
  • Boost.System;
  • Boost.Asio.

The job of application is calculate a “Gaussian Blur (image-blurring filter)”, thus the application will launch 'n' tasks to calculate in parallel using a thread pool.

4. Common Application

4.1 The base of common application looks like

C++
#include <boost\application.hpp>

class myapp
{
public:
   int operator()(const std::vector< application_ctrl::string_type >& args, 
      application_ctrl& ctrl)
   {
      ctrl.wait_for_termination_request();
      return 0;
   }
};

int main(int argc, char* argv[])
{
   return application::common_app<myapp>( application::args(argc, argv) )();
}

4.2 Application Class Natural/Extended Interface 

Boost.Application provides two kinds of client application class interface:

  • Natural Interface;
  • Extended Interface.

The natural interface hides a lot of details and provides an easy and simple way to build a common application. If user need more control, the extended interface must be used.

The above example use “Natural Interface” to know more about extended interface refer to library documentation.

4.3 Error Handler

Boost.Application provides two ways to handle errors; one throws an exception of type boost::system::system_error and another receives a boost::system::error_code variable ec that would be set to the result of the operation, and no thrown an exception.

In the last article the version that throws an exception has been addressed, here the version that uses ‘ec’ is shown

C++
int main(int argc, char* argv[])
{
   boost::system::error_code ec;
   int ret = application::common_app<myapp>( application::args(argc, argv), ec )();

   if(ec)
   {
      std::cerr << ec.message() << std::endl;
   }

   return ret;
}

5. Build a worker

The worker will calculate the “Gaussian Blur (image-blurring filter)”, it must be a functor. If you want you can change the behavior of “Gaussian Blur” to any thing here.

5.1 Gaussian Blur Worker

C++
// worker class that calculate gaussian blur
// http://en.wikipedia.org/wiki/Gaussian_blur
template< int kernelRadius = 3> 
struct gaussian_blur
{
   typedef boost::function< void (vector< vector<double> >) > callback;

   gaussian_blur(const callback& cb)
      : callback_(cb)
   {
   }

   void operator()()
   {
      boost::timer::cpu_timer timer;

      kernel2d_ = produce_gaussian_kernel(kernelRadius);
      
      boost::timer::cpu_times const elapsed_times(timer.elapsed());

      std::cout 
         << "gaussian_blur takes:" 
         <<  format(elapsed_times, 9) 
         << ", for size: " 
         << kernelRadius 
         << std::endl;

      callback_(kernel2d_);
   }

protected:

   double gaussian (double x, double mu, double sigma)
   {
      return exp( -(((x-mu)/(sigma))*((x-mu)/(sigma)))/2.0 );
   }

   vector< vector<double> > produce_gaussian_kernel (int kernelRadius) 
   {
      // get kernel matrix
      vector< vector<double> > kernel2d ( 2*kernelRadius+1, vector<double>(2*kernelRadius+1) );

      // determine sigma
      double sigma = kernelRadius/2.;

      // fill values
      double sum = 0;
      for (int row = 0; row < kernel2d.size(); row++)
      {
         for (int col = 0; col < kernel2d[row].size(); col++) 
         {
            kernel2d[row][col] = gaussian(row, kernelRadius, sigma) * gaussian(col, kernelRadius, sigma);
            sum += kernel2d[row][col];
         }
      }

      // normalize kernel, or the image becomes dark 
      for (int row = 0; row < kernel2d.size(); row++)
         for (int col = 0; col < kernel2d[row].size(); col++)
            kernel2d[row][col] /= sum;

      return kernel2d;
   }

private:

   callback callback_;
   vector< vector<double> > kernel2d_;
};

6. Build a work queue (thread-pool)

To build a work queue, the Boost.Asio and Boost.Thread will be used.

The asio io_service we will hold a pool of threads, and a task will be sent to it, and will get executed by one of the threads in the pool.

The task must be a functor, because of this the gaussian_blur is a functor class.

6.1 Work Queue

C++
template <int NWorkers = 0>
class work_queue
{
public:

   work_queue()
   {
      work_ctrl_ = new boost::asio::io_service::work(io_service_);

      int workers = boost::thread::hardware_concurrency();
      if(NWorkers > 0)
         workers = NWorkers;
      
      for (std::size_t i = 0; i < workers; ++i)
      {
         threads_.create_thread(boost::bind(&asio::io_service::run, &io_service_));
      }
   }

   virtual ~work_queue()
   {
      delete work_ctrl_;
   }

   template <typename TTask>
   void add_task(TTask task)
   {
      // c++11
      // io_service_.dispatch(std::move(task));
      io_service_.dispatch(task);
   }

private:

   boost::asio::io_service io_service_;
   boost::thread_group threads_;

   boost::asio::io_service::work *work_ctrl_;
};

7. Assembly all parts on Boost.Application

Now is time to join all parts on the Boost.Application functor class.

7.1 Application Class

C++
// application class
class myapp : work_queue<0> 
{
public:
   
   void add_result(vector< vector<double> > kernel2d)
   {
      boost::lock_guard<boost::mutex> lock(mutex_);

      task_count_++;

      result_.push_back(kernel2d);

      if(task_count_== 3)
      {
         cout << "all tasks are completed, waiting ctrl-c to display the results..." << endl;
      }
   }

   int operator()(const std::vector< application::application_ctrl::string_type >& args, 
                  application::application_ctrl& ctrl)
   {
      // your application logic here!
      task_count_ = 0;

      // our tasks
      add_task(gaussian_blur<3>( boost::bind<void>( &myapp::add_result, this, _1 ))); 
      add_task(gaussian_blur<6>( boost::bind<void>( &myapp::add_result, this, _1 ))); 
      add_task(gaussian_blur<9>( boost::bind<void>( &myapp::add_result, this, _1 ))); 
     
      ctrl.wait_for_termination_request();

      return 0;
   }
   
   int stop()
   {
      std::cout << "Result..." << std::endl;

      for(int i = 0; i < result_.size(); ++i)
      {
         cout << i << " : -----------------------" << std::endl;

         vector< vector<double> > & kernel2d = result_[i];

         for (int row = 0; row < kernel2d.size(); row++) 
         {
            for (int col = 0; col < kernel2d[row].size(); col++)
            {
               cout << setprecision(5) << fixed << kernel2d[row][col] << " ";
            }
            cout << endl;
         }
      }

      return 1;
   }

private:

   boost::mutex mutex_;  
   vector< vector< vector<double> > > result_;

   int task_count_;
   
}; // myapp

The myapp (Application class) was extended from work_queue. The work_queue will have will have the same number of threads that the processor (cores) on machine. You can specify a different number here, e.g.: work_queue<10>. This cause to the pool hold 10 threads.

The “add_result” is the callback method that will receive the result of tasks. It holds a counter (task_count_) that enable we know when all work is done.

7.2 Adding task to pool

We do this on application functor. Tree tasks are added. E.g.:

C++
// our tasks
add_task(gaussian_blur<3>( boost::bind<void>( &myapp::add_result, this, _1 ))); 
add_task(gaussian_blur<6>( boost::bind<void>( &myapp::add_result, this, _1 ))); 
add_task(gaussian_blur<9>( boost::bind<void>( &myapp::add_result, this, _1 )));

Here we can have any number of tasks, whit any type of workers.

7.3 Execution

When our application is executed we will get some think like this:

gaussian_blur takes: 0.000143487s wall, 0.000000000s user + 0.000000000s system= 0.000000000s CPU (n/a%), for size: 3
gaussian_blur takes: 0.000330720s wall, 0.000000000s user + 0.000000000s system= 0.000000000s CPU (n/a%), for size: 6
gaussian_blur takes: 0.000581997s wall, 0.000000000s user + 0.000000000s system= 0.000000000s CPU (n/a%), for size: 9
all tasks are completed, waiting ctrl-c to display the results...
Result...
0 : -----------------------
0.00134 0.00408 0.00794 0.00992 0.00794 0.00408 0.00134
0.00408 0.01238 0.02412 0.03012 0.02412 0.01238 0.00408
0.00794 0.02412 0.04698 0.05867 0.04698 0.02412 0.00794
0.00992 0.03012 0.05867 0.07327 0.05867 0.03012 0.00992
0.00794 0.02412 0.04698 0.05867 0.04698 0.02412 0.00794
0.00408 0.01238 0.02412 0.03012 0.02412 0.01238 0.00408
0.00134 0.00408 0.00794 0.00992 0.00794 0.00408 0.00134
1 : -----------------------
0.00034 0.00063 0.00104 0.00154 0.00203 0.00240 0.00254 0.00240 0.00203 0.00154 0.00104 0.00063 0.00034
0.00063 0.00117 0.00192 0.00284 0.00375 0.00443 0.00468 0.00443 0.00375 0.00284 0.00192 0.00117 0.00063
0.00104 0.00192 0.00317 0.00468 0.00618 0.00730 0.00772 0.00730 0.00618 0.00468 0.00317 0.00192 0.00104
0.00154 0.00284 0.00468 0.00691 0.00912 0.01077 0.01139 0.01077 0.00912 0.00691 0.00468 0.00284 0.00154
0.00203 0.00375 0.00618 0.00912 0.01204 0.01422 0.01503 0.01422 0.01204 0.00912 0.00618 0.00375 0.00203
0.00240 0.00443 0.00730 0.01077 0.01422 0.01680 0.01776 0.01680 0.01422 0.01077 0.00730 0.00443 0.00240
0.00254 0.00468 0.00772 0.01139 0.01503 0.01776 0.01878 0.01776 0.01503 0.01139 0.00772 0.00468 0.00254
0.00240 0.00443 0.00730 0.01077 0.01422 0.01680 0.01776 0.01680 0.01422 0.01077 0.00730 0.00443 0.00240
0.00203 0.00375 0.00618 0.00912 0.01204 0.01422 0.01503 0.01422 0.01204 0.00912 0.00618 0.00375 0.00203
0.00154 0.00284 0.00468 0.00691 0.00912 0.01077 0.01139 0.01077 0.00912 0.00691 0.00468 0.00284 0.00154
0.00104 0.00192 0.00317 0.00468 0.00618 0.00730 0.00772 0.00730 0.00618 0.00468 0.00317 0.00192 0.00104
0.00063 0.00117 0.00192 0.00284 0.00375 0.00443 0.00468 0.00443 0.00375 0.00284 0.00192 0.00117 0.00063
0.00034 0.00063 0.00104 0.00154 0.00203 0.00240 0.00254 0.00240 0.00203 0.00154 0.00104 0.00063 0.00034
2 : -----------------------
0.00015 0.00023 0.00034 0.00047 0.00062 0.00077 0.00091 0.00103 0.00111 0.00114 0.00111 0.00103 0.00091 0.00077 0.00062 0.00047 0.00034 0.00023 0.00015
0.00023 0.00036 0.00052 0.00071 0.00094 0.00117 0.00139 0.00157 0.00169 0.00174 0.00169 0.00157 0.00139 0.00117 0.00094 0.00071 0.00052 0.00036 0.00023
0.00034 0.00052 0.00075 0.00103 0.00136 0.00169 0.00201 0.00228 0.00245 0.00251 0.00245 0.00228 0.00201 0.00169 0.00136 0.00103 0.00075 0.00052 0.00034
0.00047 0.00071 0.00103 0.00142 0.00187 0.00233 0.00277 0.00314 0.00338 0.00347 0.00338 0.00314 0.00277 0.00233 0.00187 0.00142 0.00103 0.00071 0.00047
0.00062 0.00094 0.00136 0.00187 0.00245 0.00306 0.00364 0.00412 0.00444 0.00455 0.00444 0.00412 0.00364 0.00306 0.00245 0.00187 0.00136 0.00094 0.00062
0.00077 0.00117 0.00169 0.00233 0.00306 0.00383 0.00455 0.00514 0.00554 0.00568 0.00554 0.00514 0.00455 0.00383 0.00306 0.00233 0.00169 0.00117 0.00077
0.00091 0.00139 0.00201 0.00277 0.00364 0.00455 0.00540 0.00611 0.00659 0.00675 0.00659 0.00611 0.00540 0.00455 0.00364 0.00277 0.00201 0.00139 0.00091
0.00103 0.00157 0.00228 0.00314 0.00412 0.00514 0.00611 0.00692 0.00745 0.00764 0.00745 0.00692 0.00611 0.00514 0.00412 0.00314 0.00228 0.00157 0.00103
0.00111 0.00169 0.00245 0.00338 0.00444 0.00554 0.00659 0.00745 0.00802 0.00822 0.00802 0.00745 0.00659 0.00554 0.00444 0.00338 0.00245 0.00169 0.00111
0.00114 0.00174 0.00251 0.00347 0.00455 0.00568 0.00675 0.00764 0.00822 0.00843 0.00822 0.00764 0.00675 0.00568 0.00455 0.00347 0.00251 0.00174 0.00114
0.00111 0.00169 0.00245 0.00338 0.00444 0.00554 0.00659 0.00745 0.00802 0.00822 0.00802 0.00745 0.00659 0.00554 0.00444 0.00338 0.00245 0.00169 0.00111
0.00103 0.00157 0.00228 0.00314 0.00412 0.00514 0.00611 0.00692 0.00745 0.00764 0.00745 0.00692 0.00611 0.00514 0.00412 0.00314 0.00228 0.00157 0.00103
0.00091 0.00139 0.00201 0.00277 0.00364 0.00455 0.00540 0.00611 0.00659 0.00675 0.00659 0.00611 0.00540 0.00455 0.00364 0.00277 0.00201 0.00139 0.00091
0.00077 0.00117 0.00169 0.00233 0.00306 0.00383 0.00455 0.00514 0.00554 0.00568 0.00554 0.00514 0.00455 0.00383 0.00306 0.00233 0.00169 0.00117 0.00077
0.00062 0.00094 0.00136 0.00187 0.00245 0.00306 0.00364 0.00412 0.00444 0.00455 0.00444 0.00412 0.00364 0.00306 0.00245 0.00187 0.00136 0.00094 0.00062
0.00047 0.00071 0.00103 0.00142 0.00187 0.00233 0.00277 0.00314 0.00338 0.00347 0.00338 0.00314 0.00277 0.00233 0.00187 0.00142 0.00103 0.00071 0.00047
0.00034 0.00052 0.00075 0.00103 0.00136 0.00169 0.00201 0.00228 0.00245 0.00251 0.00245 0.00228 0.00201 0.00169 0.00136 0.00103 0.00075 0.00052 0.00034
0.00023 0.00036 0.00052 0.00071 0.00094 0.00117 0.00139 0.00157 0.00169 0.00174 0.00169 0.00157 0.00139 0.00117 0.00094 0.00071 0.00052 0.00036 0.00023
0.00015 0.00023 0.00034 0.00047 0.00062 0.00077 0.00091 0.00103 0.00111 0.00114 0.00111 0.00103 0.00091 0.00077 0.00062 0.00047 0.00034 0.00023 0.00015
Press any key to continue . . .

8. Conclusion

In this article I showed that Boost.Application can be integrated/extended easily with other libraries. Due to the design of Boost.Application, the user can add other features to functor class easily.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)