timer实现

实现一个 timer

前段时间写过一篇 blog 谈到 用 timer 驱动游戏 的一个想法。当 timer 被大量使用之后,似乎自己实现一个 timer 比用系统提供的要放心一些。最近在重构以前的代码,顺便也重新实现了一下 timer 模块。

这次出于谨慎,查了一些资料,无意中搜到这样一篇文章:Linux内核的时钟中断机制 。真是一个不错的设计啊 :D 和我的 timer 实现的思路是一致的,但是在细节上要优秀。

linux 的这个实现方法的优点是把事件按回调时间距离现在的远近分成了多级。我早先的实现考虑到游戏通常不会设置很长远的事件,所以只分了两级。事实上,巧妙的安排每级的数组容量,利用取模操作,处理的速度非常的快,不会因为只分两级或是分成更多级别而受到影响。

昨天晚上重写了一遍 timer 模块,居然只用了 100 行代码就完成了,篇幅较上次写的大为缩短。

我的实现方法和那篇文章中所述 linux 的实现有所区别,这并不是因为我的算法更优秀,而只是因为要解决的问题更简单罢了:

我认为实际上 struct list_head vec[TVR_SIZE]; 这里这个数组只需要开 [TVR_SIZE-1] 大小就够了。因为每级的数组的第 0 项永远都会为空,其内容全部包含在近一级别的各个数组中。而全部 5 个数组正好覆盖 [0,0xffffffff] 。

cascade_timers 这个操作并不需要每次 run 的时候都做,而只需要在恰当的时刻一次做了即可。我们在 add_timer时不需要理会相对时刻,而只需要处理绝对时刻的事件。

这样做存在两个潜在的问题,一是 run 的时候,速度有所波动,特定时刻需要处理额外的 cascade_timers 操作,比起每次每次都调用 cascade_timers 来说,这个时刻处理的数据量会增加。但我个人认为还不至于造成被人感觉的到的停顿感;二是采用绝对时刻的话,32bit 来表示时间值对于太长时间有可能溢出。好在游戏 client 程序不需要 7*24 小时工作,40 亿个 ticks 足够了。

另外,我认为 del_timer 的需求是完全多余的。如果真的有杀掉事先注册的事件的需求,我们完全可以由 timer 的参数来决定在它被触发的时候是否需要被 cancel 掉。而增加从外面主动杀掉的方法,只会增加接口的复杂性。取消这个设计后,代码会简洁很多。至少在内部实现上,不再需要双向链表。

最近一两年的开发经验让我感觉到,通常游戏中用到的 timer 回调函数往往只有唯一的一个,而仅靠参数就可以区分要做的事情。(这得益于脚本的嵌入,真正的回调函数并不需要是一个独立的 C 函数,而是一个脚本函数)

所以,我们并不需要在 timer 结构中纪录下 callback 函数指针,而只需要在 run 的时候统一传入一个即可。这样的设计比之 linux 的实现有更多的灵活性。如果真的需要支持不同的 C 函数回调,完全可以把函数指针填到参数中。因为大多数情况下,参数并不是一个数字,而是一个结构指针,如果让回调函数去负责回收内存,设计就略显丑陋了。

最终,我的 add_timer 原型只需要这样:void add_timer(void *arg, int time) 。而 run_timer_list 则需要多传递一个 callback function 。如果需要自定义的 c callback function ,可以扩展 arg 结构。例如:

struct timer_arg {
    void (*callback)(int arg);
    int arg;
    int cancel;
};

static void 
timer_callback(void *arg)
{
    struct timer_arg *a=(struct timer_arg*)arg;
    if (!a->cancel) {
        a->callback(a->arg);
    }
    free(a);
}

/* 可以使用 run_timer_list(timer_callback); 来处理 timer */
微笑

在timer设计中,假如时间精确度大于500毫米,就可以认为是bug。

我们知道,NSTimer的可以精确到50-100毫秒,假如需要更精确的timer,应该如何实现呢?

参考资料

NSTimer你真的会用了吗

Experiments with precise timing in iOS

High Precision Timers in iOS / OS X

我是否真的需要一个更精准的timer?

不要使用精准的timer除非你知道你确认你真的有必要这么做。因为这意味着消耗更多的CPU循环和电量。一次性只能激活有限数量的高精准timer,当尝试使用太多的精准timer,所有的timer都丧失了准确性。

使用示例

使用mach内核级的函数可以使用mach_absolute_time()获取到CPU的tickcount的计数值,可以通过”mach_timebase_info”函数获取到纳秒级的精确度 。然后使用mach_wait_until(uint64_t deadline)函数,直到指定的时间之后,就可以执行指定任务了。

关于数据结构mach_timebase_info的定义如下:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct mach_timebase_info {uint32_t numer;uint32_t denom;};  

下面是一个demo:

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <mach/mach.h>  
  2. #include <mach/mach_time.h>  
  3.   
  4. static const uint64_t NANOS_PER_USEC = 1000ULL;  
  5. static const uint64_t NANOS_PER_MILLISEC = 11000ULL * NANOS_PER_USEC;  
  6. static const uint64_t NANOS_PER_SEC = 11000ULL * NANOS_PER_MILLISEC;  
  7.   
  8. static mach_timebase_info_data_t timebase_info;  
  9.   
  10. static uint64_t nanos_to_abs(uint64_t nanos) {  
  11.     return nanos * timebase_info.denom / timebase_info.numer;  
  12. }  
  13.   
  14. void example_mach_wait_until(int seconds)  
  15. {  
  16.     mach_timebase_info(&timebase_info);  
  17.     uint64_t time_to_wait = nanos_to_abs(seconds * NANOS_PER_SEC);  
  18.     uint64_t now = mach_absolute_time();  
  19.     mach_wait_until(now + time_to_wait);  
  20. }  



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值