How to do Asynchronous Operations using boost::asio?

Boost::asio is primarily designed for accomplishing time-consuming networking I/O (Input/Output) operations in an asynchronous way. I/O operations over network usually takes considerable time to complete. In synchronous mode, if you call a function that does I/O operation over network, the calling thread blocks until the I/O operation is complete. This is not acceptable in many situations. For example, if a UI (User Interface) program calls such a function and the function blocks for some time, the UI will freeze for that duration.

Boost::asio came out with a solution that the main thread (or calling thread) will not block for the time consuming I/O operation. There will be a pool of threads, also known as worker threads, that will do the job and notify when the job is done.

The time consuming operation does not have a network I/O operation. We can apply this concept to any task.

This is our (fake) time consuming function.

void DoSomething(int x)
{
  g_lock.lock();
  std::cout << "Start work for " << x*10 << "ms in thread: " << boost::this_thread::get_id()
                 << std::endl;
  g_lock.unlock();

  boost::this_thread::sleep( boost::posix_time::milliseconds(x * 10));

  g_lock.lock();
  std::cout << "Work finished after " << x*10 << "ms in thread: " << boost::this_thread::get_id()
                 << std::endl;
  g_lock.unlock();
}

This function simply waits for some time based on the parameter passed as x. If the function is called with 5, the the function will sllep for 50 milliseconds. If we simply call this doSomething() from the main(), the main() will block until doSomething() returns.

Now we’ll see how to write a program to run the time consuming function doSomething() asynchronously with the help of boost::asio.

The main function will always be free even when the function doSomething() waits in one of the worker threads. We would also be able to call the function multiple times. Multiple such calls can get executed by the pool of worker threads at the same time.

The Program to Async Operation Using Boost::asio

#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include <boost/random.hpp>

boost::mutex g_lock;

void WorkerThread( boost::shared_ptr< boost::asio::io_service > io_service )
{
  io_service->run();
}

void DoSomething(int x)
{
  g_lock.lock();
  std::cout << "Start work for " << x*10 << "ms in thread: " << boost::this_thread::get_id()
                 << std::endl;
  g_lock.unlock();

  boost::this_thread::sleep( boost::posix_time::milliseconds(x * 10));

  g_lock.lock();
  std::cout << "Work finished after " << x*10 << "ms in thread: " << boost::this_thread::get_id()
                 << std::endl;
  g_lock.unlock();
}

int main()
{
  boost::shared_ptr < boost::asio::io_service > io_service
     ( new boost::asio::io_service );
  boost::shared_ptr  work
     ( new boost::asio::io_service::work ( *io_service ) );
 
  boost::thread_group worker_threads;

  for(int i = 0; i < 5; i++)
  {
    worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
  }

  boost::this_thread::sleep( boost::posix_time::milliseconds(1000));

  boost::random::mt19937 rng;
  boost::random::uniform_int_distribution<> one_to_nine(1, 9);

  for(int i = 0; i < 10; i++) {
    io_service->post( boost::bind( &DoSomething, one_to_nine(rng)));
  }

  work.reset();
  worker_threads.join_all();

  return 1;
}

In main(), we created the io_service object and a work object such that io_service.run() does not terminate for lack of work.

Then we created a pool of 5 worker threads.

  for(int i = 0; i < 5; i++)
  {
    worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
  }

Then we posted 10 works. Our work is basically calling doSomething() function with a random number between 1 to 9.

$ ./asyncworktest
Start work for 80ms in thread: 7ff3c0570700
Start work for 20ms in thread: 7ff3c1d73700
Start work for 90ms in thread: 7ff3c0d71700
Start work for 80ms in thread: 7ff3c1572700
Start work for 20ms in thread: 7ff3bfd6f700
Work finished after 20ms in thread: 7ff3bfd6f700
Start work for 90ms in thread: 7ff3bfd6f700
Work finished after 20ms in thread: 7ff3c1d73700
Start work for 90ms in thread: 7ff3c1d73700
Work finished after 80ms in thread: 7ff3c1572700
Start work for 20ms in thread: 7ff3c1572700
Work finished after 80ms in thread: 7ff3c0570700
Start work for 60ms in thread: 7ff3c0570700
Work finished after 90ms in thread: 7ff3c0d71700
Start work for 30ms in thread: 7ff3c0d71700
Work finished after 20ms in thread: 7ff3c1572700
Work finished after 90ms in thread: 7ff3c1d73700
Work finished after 90ms in thread: 7ff3bfd6f700
Work finished after 30ms in thread: 7ff3c0d71700
Work finished after 60ms in thread: 7ff3c0570700

If we look into this output carefully, then we’ll realize how the works got executed. From the first 5 lines, we can see that 5 threads execute 5 works simultaneously.

Thread 7ff3bfd6f700 finished its work first because it got a work to wait 20ms. Immediately it got another work to wait 90 ms.
Thread 7ff3bfd6f700 finished the first set of works because it got a work to wait for 90ms.
Similar way all 10 works got executed by 5 different threads.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
2
2