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<detail::deadline_timer_service<TimeTraits>>
這個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調用wait或者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的定時器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章