Boost.ASIO源码:deadline_timer源码级解析(一)

deadline_timer相关类介绍

deadline_timer众所周知,是asio的一个核心定时器,支持同步定时触发和异步定时触发。具体有什么功能如何使用这里不作介绍,本文主要从deadline_timer的wait和async_wait入手,解释deadline_timer的实现逻辑。
先解释下deadline_timer的大致结构。deadline_timer实际上是别名,它的真正名字叫basic_deadline_timer

/// Typedef for the typical usage of timer. Uses a UTC clock.
typedef basic_deadline_timer<boost::posix_time::ptime> deadline_timer;

这里的模板类表示表达时间的类型,对于这个时间类型这里不深究,知道它用来处理时间就行了。
而basic_deadline_timer继承自basic_io_object>
这个deadline_timer_service是deadline_timer的服务类,众所周知,想在io_service中搞事,都得通过服务类来进行操作,说简单点,就是basic_deadline_timer中的函数实际上都是调用deadline_timer_service的接口
而basic_io_object中实际上持有一个deadline_timer_service对象和另一个数据对象(我也不知道怎么形容这个,暂称为数据对象)。下面源码中的IoObjectService模板参数就是deadline_timer_service

template <typename IoObjectService,  // 这个IoObjectService就是deadline_timer_service
    bool Movable = detail::service_has_move<IoObjectService>::value>
class basic_io_object
{
public:
  /// The type of the service that will be used to provide I/O operations.
  typedef IoObjectService service_type;

  /// The underlying implementation type of I/O object.
  typedef typename service_type::implementation_type implementation_type;  // 这个就是数据对象类型

// 。。。

private:
  // The service associated with the I/O object.
  service_type& service_;

  /// The underlying implementation of the I/O object.
  implementation_type implementation_;   // 数据对象

这个implementation_type就是我所说的数据对象(的类型)了。直接看到deadline_timer_service中implementation_type的定义:

  // The implementation type of the timer. This type is dependent on the
  // underlying implementation of the timer service.
  struct implementation_type
    : private boost::asio::detail::noncopyable
  {
    time_type expiry;   // 表示时间,time_type是模板参数
    bool might_have_pending_waits;   // 表明是否还有要等待的操作
    typename timer_queue<Time_Traits>::per_timer_data timer_data;  // 用于存放到epoll_reactor的定时器队列中
  };

上面的per_timer_data可能不是很好理解,deadline_timer调用async_wait后会往epoll_reactor的定时器队列中存一个元素,这个元素的类型就是这个per_timer_data。而这个epoll_reactor就是一个触发器,基于篇幅原因,这里就不多说了,仅需要知道这是一个类似epoll的包装类就行了。(类似epoll只是说部分功能相似,这两者区别还是很大的)为了方便理解,还是上源码:

class timer_queue : public timer_queue_base
{
public:

	class per_timer_data
	{
	  public:
	    per_timer_data() :
	      heap_index_((std::numeric_limits<std::size_t>::max)()),
	      next_(0), prev_(0)
	    {
	    }
	
	  private:
	    friend class timer_queue;
	
	    // The operations waiting on the timer.
	    op_queue<wait_op> op_queue_;   // 该定时器所绑定的处理函数
	
	    // The index of the timer in the heap.
	    std::size_t heap_index_;    // 在最小堆中的下标
	
	    // Pointers to adjacent timers in a linked list.
	    per_timer_data* next_;      // 用于维护链表结构,下同
	    per_timer_data* prev_;
	};

//...

private:
// The head of a linked list of all active timers.
  per_timer_data* timers_;  // 双向链表

  struct heap_entry
  {
    // The time when the timer should fire.
    time_type time_;
    // The associated timer with enqueued operations.
    per_timer_data* timer_;
  };
  
 // The heap of timers, with the earliest timer at the front.
 std::vector<heap_entry> heap_;  // 最小堆

可以看到在这个time_queue中,per_timer_data以两种方式维护,一个是双向链表,表头就是timers_,另一个是最小堆,就是heap_。
这样子设计是有原因的,用双向链表维护可以保证添加定时器的顺序性,而最小堆是以定时器触发的事件维护的,堆首的定时器将是最快触发的。

basic_deadline_timer源码解析

首先来看看当我们写下如下代码时具体发生了什么。

boost::asio::deadline_timer t(io, boost::posix_time::seconds(5)); 

此时将调用basic_deadline_timer的构造函数:

basic_deadline_timer(boost::asio::io_context& io_context,
      const duration_type& expiry_time)
    : basic_io_object<detail::deadline_timer_service<TimeTraits>>(io_context)
  {
    boost::system::error_code ec;
    this->get_service().expires_from_now(
        this->get_implementation(), expiry_time, ec);
    boost::asio::detail::throw_error(ec, "expires_from_now");  // 如果ec非空,会抛出异常,否则啥事不干。
  }

这里的get_service就得到了我们前面提到的basic_io_object(basic_deadline_timer的父类)中的service_成员,即该定时器的服务类。expires_from_now,甚至包括其它的expires_XXX都是获取或者修改这个定时器的时间信息,也就是存取我前面所说的数据对象implementation_(跟service_放一起的)。由此看,实际上定时器的构造非常简单。
那个throw_error的写法贯穿于整个asio,不同于往常我们习惯的try-catch写法,这里采用的类似于errno的写法,许多操作函数都需要传入一个error_code对象,返回时会重新初始化该对象,如果该对象非空则会抛出异常。

同步调用——wait

正如前面所说,basic_deadline_timer自己是干不了啥事的,还是得调用deadline_timer_service的接口方法:

  void wait()
  {
    boost::system::error_code ec;
    this->get_service().wait(this->get_implementation(), ec);
    boost::asio::detail::throw_error(ec, "wait");
  }

再看deadline_timer_service中的wait方法:

  // Perform a blocking wait on the timer.
  void wait(implementation_type& impl, boost::system::error_code& ec)
  {
    time_type now = Time_Traits::now();
    ec = boost::system::error_code();
    while (Time_Traits::less_than(now, impl.expiry) && !ec)
    {
      this->do_wait(Time_Traits::to_posix_duration(
            Time_Traits::subtract(impl.expiry, now)), ec);
      now = Time_Traits::now();
    }
  }
  
  template <typename Duration>
  void do_wait(const Duration& timeout, boost::system::error_code& ec)
  {
    ::timeval tv;
    tv.tv_sec = timeout.total_seconds();
    tv.tv_usec = timeout.total_microseconds() % 1000000;
    socket_ops::select(0, 0, 0, 0, &tv, ec);
  }

其实源码已经很好理解了,用了类似pthread_cond_t条件变量触发的写法,在循环判断内进行阻塞等待操作。
这里阻塞等待用的是select的定时机制,这里的select实际上是没有传入任何描述符的。
实际上这里的do_wait源码我删去了部分条件编译包裹的代码,那部分代码是在windows系统上运行的,在windows上当然就不用select了,直接用的std::this_thread::sleep_for方法。

异步调用——async_wait

异步调用相对就复杂一点了,因为要传入回调函数,还需要epoll_reactor的协助。
首先看basic_deadline_timer中的async_wait函数:

  template <typename WaitHandler>
  BOOST_ASIO_INITFN_RESULT_TYPE(WaitHandler,
      void (boost::system::error_code))
  async_wait(WaitHandler&& handler)
  {
    // If you get an error on the following line it means that your handler does
    // not meet the documented type requirements for a WaitHandler.
    BOOST_ASIO_WAIT_HANDLER_CHECK(WaitHandler, handler) type_check;

    async_completion<WaitHandler,
      void (boost::system::error_code)> init(handler);

    this->get_service().async_wait(this->get_implementation(),
        init.completion_handler);

    return init.result.get();
  }

看起来很长,实际上逻辑很简单。第8行的宏函数,实际上就是检验传进来的WaitHandler这个函数是否符合要求,具体代码有点复杂,这里就不贴了,大致思路是采用static_cast能否正常转化来验证,这里面用到了静态断言。
当然,这个函数的主体还是得去调用deadline_timer_service的async_wait。此外,还有async_completion的处理,这里引用下async_completion构造函数的官方注释:

   /**
   * The constructor creates the concrete completion handler and makes the link
   * between the handler and the asynchronous result.
   */

大致意思就是对这个传进来的回调函数进行下处理,再将其与一个异步返回结果进行绑定。颇像future的机制啊。
然而实际上,让我很纳闷的是,在return语句那一行,返回的init.result.get(),这个get()函数实际上是空的…

template <typename Handler>
class async_result<Handler>
{
public:
  typedef void type;
  explicit async_result(Handler&) {}
  type get() {}
};

不知道是不是我理解有误,反正我是没弄懂这写法。。
再看到deadline_timer_service的async_wait:

  template <typename Handler>
  void async_wait(implementation_type& impl, Handler& handler)
  {
    // Allocate and construct an operation to wrap the handler.
    typedef wait_handler<Handler> op;
    typename op::ptr p = { boost::asio::detail::addressof(handler),
      op::ptr::allocate(handler), 0 };
    p.p = new (p.v) op(handler);

    impl.might_have_pending_waits = true;

	// shceduler_是定时器服务的调度器,是epoll_reactor对象
    scheduler_.schedule_timer(timer_queue_, impl.expiry, impl.timer_data, p.p);
    p.v = p.p = 0;
  }

op::ptr这一段实际上我没看懂,大致意思应该是对handler这个回调函数进行一层包装,这个包装对象是动态分配的,可以看到p指针最后被清0,因为在schedule_timer中,该包装对象的负责权已经被托管给传入该函数的deadline_timer_service的timer_queue_了。在此再补充一下,实际上deadline_timer_service有两个成员:

  // The queue of timers.
  timer_queue<Time_Traits> timer_queue_;   // 维护所有的定时器

  // The object that schedules and executes timers. Usually a reactor.
  timer_scheduler& scheduler_;   // deadline_timer_service服务的异步调度器。这里timer_scheduler就是epoll_reacotr

接下来再看schedule_timer函数:

template <typename Time_Traits>
void epoll_reactor::schedule_timer(timer_queue<Time_Traits>& queue,
    const typename Time_Traits::time_type& time,
    typename timer_queue<Time_Traits>::per_timer_data& timer, wait_op* op)
{
  mutex::scoped_lock lock(mutex_);

  if (shutdown_)
  {
    scheduler_.post_immediate_completion(op, false);
    return;
  }

  bool earliest = queue.enqueue_timer(time, timer, op);//将定时器添加进队列,这个队列是deadline_timer_service的timer_queue_成员
  scheduler_.work_started();
  if (earliest)
    update_timeout();  // ,如果当前定时器的触发时间最早,则更新epoll_reactor的timer_fd
}

void epoll_reactor::update_timeout()
{
  if (timer_fd_ != -1)
  {
    itimerspec new_timeout;
    itimerspec old_timeout;
    int flags = get_timeout(new_timeout);
    timerfd_settime(timer_fd_, flags, &new_timeout, &old_timeout);
    return;
  }
  interrupt();
}

前面用于包装回调函数的wait_handler是wait_op的子类,而wait_op是scheduler_operation的子类。scheduler_operation代表所有这种回调函数的包装。
需要注意的是这里的scheduler_不再是前面的scheduler_了,这里的scheduler_是epoll_reactor的成员,是scheduler对象(scheduler就是io_service的实现类)。
这个函数先判断当前的epoll_reactor是否处于关闭状态,epoll_reactor关闭时是不会进行任何异步监听的。如果epoll_reactor已关闭,则把该操作函数交给scheduler(即io_service)来处理。具体如何处理我们后面的博客再讲,这个逻辑有点复杂。如果epoll_reactor处于正常的开启状态,则将该定时器添加到deadline_timer_service的timer_queue_队列中,然后告诉scheduler(io_service)有新的任务来了(就是那句scheduler_.work_started()),后面scheduler会自动处理。如果当前定时器的触发时间点最早,还要更新epoll_reactor的定时器。

你可能感兴趣的:(源码阅读笔记)