Reference: tuttimer5
Based on the tutorial, the main function looks like the following:
int main()
{
boost::asio::io_context io;
printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run(); // Why do we need to call io.run() here?
t.join();
return 0;
}
I want to understand why we need to have two io.run();
in the main function.
Here is my test result:
Test 1 (call io.run() in main thread within main function):
main-Thread #32
printer-Thread #32
print1-Thread #32
Timer 1: 0
print2-Thread #32
Timer 2: 1
print1-Thread #32
Timer 1: 2
print2-Thread #32
Timer 2: 3
print1-Thread #32
Timer 1: 4
print2-Thread #32
Timer 2: 5
print1-Thread #32
Timer 1: 6
print2-Thread #32
Timer 2: 7
print1-Thread #32
Timer 1: 8
print2-Thread #32
Timer 2: 9
~printer-Thread #32
Final count is 10
Observation1: both print1
and print2
are called by main-thread.
Test 2 (NOT call io.run() in main thread within main function):
main-Thread #40
printer-Thread #40
print1-Thread #29
Timer 1: 0
print2-Thread #29
Timer 2: 1
print1-Thread #29
Timer 1: 2
print2-Thread #29
Timer 2: 3
print1-Thread #29
Timer 1: 4
print2-Thread #29
Timer 2: 5
print1-Thread #29
Timer 1: 6
print2-Thread #29
Timer 2: 7
print1-Thread #29
Timer 1: 8
print2-Thread #29
Timer 2: 9
~printer-Thread #40
Final count is 10
Observation2: Neither print1
and print2
are called by main-thread.
Note: the testing results are come from my local linux box(g++ (Ubuntu 11.1.0-1ubuntu1~18.04.1) 11.1.0)). The result looks a little different from coliru.
In that case io_context operates like a classic thread pool. Asynchronous tasks are performed somewhere on the OS side, however completion handlers are invoked on those threads where io_context::run function is running. To be more precise: every completion handler is invoked on a first free thread which io_context::run function is running on.
Question> what is the main reason that we want to call io.run);
in both main and a new thread in the original tutorial?
Thank you
None of it is required. It merely shows that tasks are distributed over all thread participating in running/polling the execution context.
In that sense, the code as written is simply the cheapest way to get 2 threads running the io context. In a more recent Boost version, one could argue that thread_pool
is cleaner code, but results in 3 threads (counting the main thread):
int main()
{
report("main") << std::endl;
boost::asio::thread_pool io(2);
printer p(io.get_executor());
io.join();
}
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
#include <thread>
namespace asio = boost::asio;
std::ostream& report(const std::string& suffix_msg)
{
auto tid = std::hash<std::thread::id>{}(std::this_thread::get_id()) % 100;
return std::cout << suffix_msg << "-Thread #" << std::setw(2)
<< std::setfill('0') << tid;
}
class printer {
public:
printer(asio::any_io_executor ex)
: strand_(asio::make_strand(ex))
, count_(0)
{
report("printer") << std::endl;
timer1_.async_wait(
std::bind(&printer::print_callback, this, "Timer 1", std::ref(timer1_)));
timer2_.async_wait(
std::bind(&printer::print_callback, this, "Timer 2", std::ref(timer2_)));
}
~printer()
{
report("~printer") << "\tFinal count is " << count_ << std::endl;
}
void print_callback(std::string_view name, asio::steady_timer& timer)
{
if (count_ < 10) {
report("print_callback") << "\t" << name << ": " << count_ << std::endl;
++count_;
timer.expires_at(timer.expiry() + asio::chrono::seconds(1));
timer.async_wait(std::bind(&printer::print_callback, this, name,
std::ref(timer)));
}
}
private:
asio::any_io_executor strand_;
int count_ = 0;
asio::steady_timer timer1_{strand_, asio::chrono::seconds(1)};
asio::steady_timer timer2_{strand_, asio::chrono::seconds(1)};
};
int main()
{
report("main") << std::endl;
boost::asio::thread_pool io(2);
printer p(io.get_executor());
io.join();
}
Prints e.g.
main-Thread #59
printer-Thread #59
print_callback-Thread #06 Timer 1: 0
print_callback-Thread #06 Timer 2: 1
print_callback-Thread #74 Timer 1: 2
print_callback-Thread #74 Timer 2: 3
print_callback-Thread #74 Timer 1: 4
print_callback-Thread #74 Timer 2: 5
print_callback-Thread #74 Timer 1: 6
print_callback-Thread #74 Timer 2: 7
print_callback-Thread #74 Timer 1: 8
print_callback-Thread #74 Timer 2: 9
~printer-Thread #59 Final count is 10