前前后后run这个函数来来往往反反复复看了不知道多少遍,对它的逻辑始终没弄明白,直到最近研究deadline_timer才恍然大悟理清了前面的一些逻辑,在此顺便总结一下,也算是填了前面几个博客一直没讲明白的一些点。(因为那时我还没完全看懂,故只能留坑了。。)
我前面所有的博客都算是这篇博客的铺垫,本文中也多次引用了我以前写的博客,需要的时候建议还是参考下,不然可能有点难以理解。
前文回顾
前面两个博客一个讲了deadline_timer的调用逻辑,一个讲了epoll_reactor的触发逻辑。其实最重要的真正处理逻辑一直没讲,而这个处理逻辑,便是在io_service::run()中。
前面两篇文章,第一篇是讲到epoll_reactor::schedule_timer就没有扩展下去了,第二篇在第一篇的基础上进行扩展,但讲到epoll_reactor::run()也没有扩展下去了。在这里我还是建议一下至少先看前面两篇文章,虽然直接看这篇文章也能对io_service::run()有所了解,但是从最外层调用开始看能对整个代码逻辑有更宏观的理解。
io_service::run的背景相关
这些其实大部分前面的博客都说过了,但在这里还是总结说明一下。
io_service是一个别名,它的本名叫io_context,而io_context继承自execution_context,代表一个上下文环境,这个execution_context持有一个service_registry对象,service_registry维护所有的服务,每种服务只会在service_registry中存在一个对象,这个主要通过use_service这个函数实现。而服务说直白点,就是调用ASIO的那些接口类所调用的逻辑处理函数集合体(当然服务里还会持有相关数据),所有的服务都继承自execution_context::service,这个基类规定,所有的服务都要持有它的运行上下文对象,即execution_context(具体点方便理解,这里就把execution_context直接看作io_service就行了)。而io_context实际上也是不干实事的,io_context有个成员叫impl_,这个成员可以理解为io_context的具体逻辑实现类,io_context几乎所有函数都是调用这个impl_的接口函数。在这里,这个impl_就是我们前面多次提到的scheduler。
所以所谓的io_service.run,它的真实逻辑在scheduler.run中。
从构造scheduler到scheduler::run()到底发生了什么
这里还提到了构造scheduler,scheduler在构造时实际上还做了一些很不明显的工作,如果忽略的话scheduler::run()的逻辑就讲不通了(这也是前面我一直没看懂的原因)。
首先看到一个最基本的deadline_timer用法,后面会以这个例子为例来讲解run的逻辑:
void Print(const boost::system::error_code &ec);
boost::asio::io_service io;
boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));
t.async_wait(Print);
io.run()
从第2行构造io_service看起,此时当然实际上执行的是io_context的空参构造函数,其中会以默认的方式构造scheduler:
io_context::io_context()
: impl_(add_impl(new impl_type(*this, BOOST_ASIO_CONCURRENCY_HINT_DEFAULT)))// 这个impl_type就是scheduler的别名
{
}
io_context::impl_type& io_context::add_impl(io_context::impl_type* impl) // 这个impl_type就是scheduler的别名
{
boost::asio::detail::scoped_ptr<impl_type> scoped_impl(impl); // 指针包裹类,销毁时会delete指针所指对象
boost::asio::add_service<impl_type>(*this, scoped_impl.get()); // 这里把scheduler添加到service_registry中
return *scoped_impl.release(); // 解除指针与所指对象的绑定关系,这样就不会释放原本所指的对象了。
// 为什么既然最后要解除绑定还要用scoped_impl这种智能指针来处理呢:考虑下中间异常退出的情况。
}
然后再看所调用的scheduler构造函数:
scheduler::scheduler(
boost::asio::execution_context& ctx, int concurrency_hint)
: boost::asio::detail::execution_context_service_base<scheduler>(ctx),
one_thread_(concurrency_hint == 1
|| !BOOST_ASIO_CONCURRENCY_HINT_IS_LOCKING(
SCHEDULER, concurrency_hint)
|| !BOOST_ASIO_CONCURRENCY_HINT_IS_LOCKING(
REACTOR_IO, concurrency_hint)),
mutex_(BOOST_ASIO_CONCURRENCY_HINT_IS_LOCKING(
SCHEDULER, concurrency_hint)),
task_(0), // 这个是epoll_reactor,此时初始化为空
task_interrupted_(true), // 一个flag,标识epoll_reactor是否处于中断状态
outstanding_work_(0), // 未完成的任务数
stopped_(false),
shutdown_(false),
concurrency_hint_(concurrency_hint)
{
}
这里只需要记住outstanding_work_这个属性就行了,这个表示未完成任务数的成员属性将是决定scheduler::run()的运行状态的关键属性。
这里可以看到在scheduler构造时实际上它的触发器是空的,那这个epoll_reactor是在哪里初始化呢——在deadline_timer的构造函数中(这里仅仅是以最前面那个例子为例,并不是说epoll_reactor只能在这里面初始化,准确说epoll_reactor在许多相关的服务类中都会触发初始化)。如前面文章所说,deadline_timer的构造实际上会导致它的服务类deadline_timer_service的构造,而deadline_timer_service的构造函数中会调用epoll_reactor::init_task(),而这个函数又会调用scheduler::init_task():
deadline_timer_service(boost::asio::io_context& io_context)