The asio library has been immensely helpful in promoting a series of patterns previously rarely seen in the lands of C++ (i.e the proactor pattern). It is therefore not surprising that people are starting to use it beyond the scope of networking.
Take for instance this recipe you can find on the official (non-boost) website:
asio::io_service io_service;In just a bunch of lines of code we have a thread pool executing any arbitrary task whenever one thread is available. No need to address the usual multi-threading issues with concurrent jobs sharing few resources: just post what you need and let io_service take care of it!
for (std::size_t i = 0; i < my_thread_count; ++i)
I used this approach recently when I needed to implement a way to asynchronously update a backup server with incoming operations.
First a bit of background: at last.fm we often use thrift to power our services. Thrift is a powerful RPC framework that works across multiple languages. When you specify the methods the server is going to answer, thrift generates both the code for your server and the client in whatever language you want. For instance this definition
void ping()will create the client class that can be used (this is simplified) this way:
void foo(1: i32 value)
apache::MyClient client;What I wanted to do is to be able to queue and execute an arbitrary number of ping and foo on a pool of MyClient objects and on a set of n threads running them.
// connection stuff
The pool of objects can be handled by a simple stack that implements the RAII pattern to safely pull objects when needed and push them back in when their job is done. I might write something about it in the future, but for the sake of simplicity I will skip that in my example. What's mostly interesting is the boost+asio part!
So here's how. First I define my BackupHandler class:
BackupHandler(int poolSize = 4)
m_pWork.reset( new boost::asio::io_service::work(m_io_service) );
for ( int i = 0; i < poolSize; ++i)
m_threadGroup.create_thread( boost::bind(&boost::asio::io_service::run, &m_io_service) );
m_pWork.reset(); // stop all!
m_threadGroup.join_all(); // wait for all completition
// this will leave immediately
template <typename TFunc>
void enqueue(TFunc fun)
boost::bind( &BackupHandler::execute<TFunc>, this, fun )
// this will be executed when one of the threads is available
template <typename TFunc>
void execute(TFunc fun)
MyClient client; // here I should use RAII to get the client resource
Note how client becomes the argument instead of the caller. This is possible because with boost::bind you can put a placeholder (_1) even on the caller when used on pointer to member functions.
I can now enqueue the functions whenever I need, thus purely asynchronously, with bind:
Make sure you use boost::protect otherwise bind will try and evaluate the placeholder before its time is due and the compiler will cry (or crash).
boost::bind(&apache::MyClient::ping, _1) // who's gonna call that? _1!
//.. other code
boost::bind(&apache::MyClient::foo, _1, 42)
// ..more stuff enqueued later..