To post or to dispatch?

We have already seen how to use the ASIO I/O Service post() method to run tasks concurrently. Actually, we could use another io_service method to get almost the same behavior: dispatch().

The subtle difference is that dispatch() could even execute directly the passed function, where post() always request to io_service to run it.

An example should clarify the matter.

[A Japanese translation of this post is available on the Faith and Brave C++ blog]

I slightly modify the original example, making it probably less immediate. Please, refer to it if you are looking for something less contrived. Now the main function of the application starts three threads, each of them running on the io_service.run() function. Then we ask the user if he wants to run a new function, or he wants to terminate the application. Finally we cleanup and terminate:
std::cout << "Main thread: " << boost::this_thread::get_id() <<  std::endl;

boost::asio::io_service ios; // 1
boost::asio::io_service::work work(ios); // 2

size_t count = 3;
boost::thread_group threads;
for(size_t i = 0; i < count; ++i)
  threads.create_thread(boost::bind(&boost::asio::io_service::run, &ios));

while(startAFunction())
  ios.post(boost::bind(fA, boost::ref(ios))); // 3

std::cout << "Stopping ASIO I/O Service ..." << std::endl;
ios.stop();
threads.join_all();
std::cout << "All threads terminated" << std::endl;
1. Instantiate a io_service object.
2. Try to comment this line: if you do it, the io_service won't have anything to run when you start the application up, so all the run() calls would happily terminate before we could submit to them anything to do.
3. We post() the function address of fA(), and a reference to ios, that would be passed to fA().

The very rough input function is here:
bool startAFunction()
{
  std::cout << "Enter a non-empty string to run A function" << std::endl;

  std::string input;
  getline(std::cin, input);
  return input.length() == 0 ? false : true;
}
If the user just press enter, the function returns false, otherwise it returns true.

And here is the function called by the main function:
void fA(boost::asio::io_service& ios)
{
  static int selector = 0;
  std::cout << boost::this_thread::get_id()
    << " starting A function" << std::endl;
  boost::this_thread::sleep(boost::posix_time::seconds(3));
  if(++selector % 2) // 1
  {
    std::cout << boost::this_thread::get_id()
      << " dispatching" <<  std::endl;
    ios.dispatch(fB);
  }
  else // 2
  {
    std::cout << boost::this_thread::get_id()
      << " posting" <<  std::endl;
    ios.post(fB);
  }
  std::cout << boost::this_thread::get_id()
    << " exiting A function" << std::endl;
}
1. The first time fA() is called, it dispatch() to the io_service a request of execution for another function, fB(), that we'll see next.
2. Here we see the alternative behavior, calling post().

The internally called function is not very interesting, at least in my implementation:
void fB()
{
  boost::this_thread::sleep(boost::posix_time::seconds(1));
  std::cout << boost::this_thread::get_id()
    << " running B function" << std::endl;
}
Running the application we'll see the difference between posting and dispatching. Since it could do it, dispatch() would execute fB() directly, so we'll see it runs in the current thread, and synchronously. On the other side, post() would ask to io_service to do the job, asynchronously in another thread, and it immediately returns the control to the caller.

Actually, in this example it doesn't make much sense using dispatch(), we could have called directly fB() saving some overhead.

No comments:

Post a Comment