快速构建MMO服务器框架(五)timer的那些事儿

     定时器管理在MMO服务器这类需要处理大量事务的系统中,往往是优化的热点。
    最简单的轮询式定时器的实现如(psudo code):
class Player { public: void loop() { if (timer.check()) { //do something; } //other timer ... } private: Timer timer; } void main() { while (true) { foreach player in playerManager { player.loop(); } sleep(0); } };
    这种设计唯一的好处是编程方式简单。
    坏处:
    当需要检测的定时器大量增加时,整体性能将会严重下降。即使手动增加一些高精度把低精度计时器过滤掉的手法,也容易导致逻辑处理堆积在某个时间点的问题。
    诱导逻辑代码堆积在loop()函数中的不良风格。
    playerManager这类容器要时时防止遍历过程中添加、删除元素造成迭代器失效的情况,不过这是另一个问题了。

    对服务器而言,定时器逻辑的特性跟客户端的区别主要在于:客户端最主要操作是图形渲染,大部分对象每帧都要更新,对这些对象增加定时器调度机制也许还会得不偿失;时间间隔长的逻辑比较少,统一轮询处理也可接受。而服务器以大批量事务处理为主,时间间隔通常都在秒级以上,轮询会导致cpu大量空转。再者如果不能良好隔离各种事务逻辑,就会导致对象的更新函数中充满了各种如 if (task && taskTimer.check()) 这样的代码,难看且低效。
    
    而在boost中,提供了和io_service结合使用的deadline_timer类。同步的定时器会导致阻塞,用处不大,这里就不引用了。以下是异步定时器的示例:(出自boost文档)
// // timer.cpp // ~~~~~~~~~ // // Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <iostream> #include <boost/asio.hpp> #include <boost/date_time/posix_time/posix_time.hpp> void print(const boost::system::error_code& /*e*/) { std::cout << "Hello, world!/n"; } int main() { boost::asio::io_service io; boost::asio::deadline_timer t(io, boost::posix_time::seconds(5)); t.async_wait(print); io.run(); return 0; }
    使用起来也很简洁。不过,异步的定时器也带来了复杂度上的增加,比如会牵涉到对象生命期的有效性之类,遇到这种情况可以结合智能指针使用。


细节和注意事项:

1.timer队列实现细节

    io_service内部有个timer_queue成员用来管理timer,不过并不是传统意义上的queue。timer在queue中有两个索引,一个是以timer地址作为索引的multi hashmap(概念上可以这么理解,实现上是hashmap + 链表),另一个是堆,按照触发时间由小到大排序,每次派发则执行 触发时间<当前时间 的timer回调。按照堆的性质,定时器添加进堆的复杂度是O(log n),不过大部分情况没那么坏(详细见下,出自wikipedia)

    If we have a heap, and we add an element, we can perform an operation known as up-heap, bubble-up, percolate-up, sift-up, or heapify-up in order to restore the heap property. We can do this in O(log n) time. (略)However, since approximately 50% of the elements are leaves and 75% are in the bottom two levels, it is likely that the new element to be inserted will only move a few levels upwards to maintain the heap. Thus, binary heaps support insertion in average constant time, O(1).

    timer的数据记录了在堆中的索引,删除也是O(log n)的时间复杂度(用来调整堆)。

    如果有大量timer高频率的插入和删除的需求,也许性能上会有影响。如果这种情况发生,可以在io_service的上层再自定制计时器管理机制。但切忌过早优化。

2.timer的生命期

    把timer添加到io_service中,实际上添加的不是timer对象本身,而是handler和时间信息。timer_queue内部对这些数据进行了包装,以timer的地址作为索引,但是并不会访问它所指的内存。所以如果在定时器触发前把timer对象删除,回调也会照常调用。可能出现的问题有:如果timer的内存回收,这段内存又刚好分配给new_timer,再把new_timer添加进io_service()中,此时调用io_service()移除new_timer则会把timer的回调也一起移除。如果需要严格确保这种情况不发生,则要控制好timer的生命期。

你可能感兴趣的:(timer,框架,IO,服务器,service,HashMap)