为什么在 asico 教程 Timer.5 示例中我们需要在主线程和新线程 `t` 中 运行 `io.run`?

Why do we need to run `io.run` in both main thread and a new thread `t` within asico tutorial Timer.5 example?

参考:tuttimer5

coliru demo

根据教程,主要函数如下所示:

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;
}

我想明白为什么我们需要在主函数中有两个io.run();。 这是我的测试结果:

测试1(在主函数内的主线程中调用io.run()):

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

观察 1:print1print2 都被主线程调用。

测试 2(不在主函数内的主线程中调用 io.run()):

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

观察 2:print1print2 都没有被主线程调用。

注:测试结果来自我本地的linux盒子(g++ (Ubuntu 11.1.0-1ubuntu1~18.04.1) 11.1.0))。结果看起来和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.

问题>我们想在原教程的主线程和新线程中调用io.run);的主要原因是什么?

谢谢

None 是必需的。它仅表明任务分布在参与 running/polling 执行上下文的所有线程上。

从这个意义上说,所编写的代码只是获得 2 个线程 运行 io 上下文的最便宜的方法。在更新的 Boost 版本中,有人可能会争辩说 thread_pool 是更简洁的代码,但会产生 3 个线程(算上主线程):

int main()
{
    report("main") << std::endl;
    boost::asio::thread_pool io(2);

    printer p(io.get_executor());
    io.join();
}

Live On Coliru

#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();
}

打印例如

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