someCoder
someCoder

Reputation: 185

Understanding how multithreading works with Boost io_service

I'm learning multithreading and Boost libraries (Asio in particular) and I'm having a hard time understanding how the following code works (slightly modified from Boost.org tutorials)

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

class printer
{
public:
  printer(boost::asio::io_service& io)
    : timer1_(io, boost::posix_time::seconds(1)),
      timer2_(io, boost::posix_time::seconds(1)),
      count_(0)
  {
    timer1_.async_wait(boost::bind(&printer::print1, this));

    timer2_.async_wait(boost::bind(&printer::print2, this));
  }

  ~printer()
  {
    std::cout << "Final count is " << count_ << std::endl;
  }

  void print1()
  {
    if (count_ < 10)
    {
      std::cout << "Timer 1: " << count_ << std::endl;
      ++count_;

      timer1_.expires_at(timer1_.expires_at() + boost::posix_time::seconds(2));

      timer1_.async_wait(boost::bind(&printer::print1, this));
    }
  }

  void print2()
  {
    if (count_ < 10)
    {
      std::cout << "Timer 2: " << count_ << std::endl;
      ++count_;

      timer2_.expires_at(timer2_.expires_at() + boost::posix_time::seconds(2));

      timer2_.async_wait(boost::bind(&printer::print2, this));
    }
  }

private:
  boost::asio::deadline_timer timer1_;
  boost::asio::deadline_timer timer2_;
  int count_;
};

void saysomething()
{
    std::string whatyasay;
    std::cin >> whatyasay;
    std::cout << "You said " << whatyasay << std::endl;
}
int main()
{
  boost::asio::io_service io;
  printer p(io);
  boost::thread t(boost::bind(&boost::asio::io_service::run, &io));
  io.run();
  std::cout << "Hey there\n";
  t.join();

  return 0;
}

Which results in the following output

Timer 1: 0
Timer 2: 1
Timer 1: 2
Timer 2: 3
Timer 1: 4
Timer 2: 5
Timer 1: 6
Timer 2: 7
Timer 1: 8
Timer 2: 9
Hey there
Final count is 10

What I would've expected from this code was that thread t would be in charge of running the io_service, meaning that other operations could take place in the meantime.

Instead, the code behaves as usual, aka, io.run "blocks" the code flow until the timers inside the printer object stop launching async_waits, so "hey there" is only printed after the timers are not working anymore.

But that's not all: from my understanding, io_services don't stop running after the run() method is called as long as there's work associated to them (be it a work object or, in this case, timers). With that said, since the thread is associated to the io_service, I wonder why the io_service would stop running in the first place: after all, the thread is "linked" to the io_service and keeps on running on its own; this is obviously linked to the fact that I clearly didn't understand what this thread is doing in the first place.

Things got even more complicated when I added the "saysomething" method into the pot: I wanted to be able to write something and having that string printed WHILE the 2 timers kept working. The code I used was the following:

int main()
{
  boost::asio::io_service io;
  printer p(io);
  boost::thread t(&saysomething);
  io.run();
  std::cout << "Hey there\n";
  t.join();

  return 0;
}

With the following result:

Timer 1: 0
Timer 2: 1
Timer 1: 2
Timer 2: 3
Timer 1: 4
Timer 2: 5
Timer 1: 6
Timer 2: 7
ghg       //<--- my input
You said ghg
Timer 1: 8
Timer 2: 9
Hey there
Final count is 10

It works fine, but now that there is no thread associated to the io_service, what was its purpose in the first place?

To sum up my 3 questions are:

Upvotes: 0

Views: 149

Answers (1)

Zuodian Hu
Zuodian Hu

Reputation: 1009

  • Why isn't the "Hey there" string immediately printed rather than waiting for the io_service to stop running?

main's thread also blocks on the io_service before printing, so "Hey there" doesn't print until the service stops.

  • How exactly does the io_service stop running if a thread is linked to it, which should be equivalent to the io_service having work to do?

The thread is not what's keeping the io_service alive, the timer tasks are. The io_service is actually the one keeping the thread alive here. The work the service has is waiting on the timers, so until the timers expire, the service has work to do.

  • Since the thread wasn't allowing the "code flow" to move forward, and linking said thread to my method instead of the io_service didn't cause any error, what was the purpose of that thread in the first place?

The purpose of calling run from a thread is to donate that calling thread to the io_service. Until run exits, the service owns that thread, and that thread is part of the service's thread pool. Any task you post to the service may be handed to that thread while it is in the service's pool. When you added the second thread, that second thread wasn't interacting with the service at all because it didn't call run. Thus, it's not part of the service's thread pool.

Upvotes: 1

Related Questions