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,应该如何实现呢?
参考资料
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的定义如下:
- struct mach_timebase_info {uint32_t numer;uint32_t denom;};
下面是一个demo:
- #include <mach/mach.h>
- #include <mach/mach_time.h>
- static const uint64_t NANOS_PER_USEC = 1000ULL;
- static const uint64_t NANOS_PER_MILLISEC = 11000ULL * NANOS_PER_USEC;
- static const uint64_t NANOS_PER_SEC = 11000ULL * NANOS_PER_MILLISEC;
- static mach_timebase_info_data_t timebase_info;
- static uint64_t nanos_to_abs(uint64_t nanos) {
- return nanos * timebase_info.denom / timebase_info.numer;
- }
- void example_mach_wait_until(int seconds)
- {
- mach_timebase_info(&timebase_info);
- uint64_t time_to_wait = nanos_to_abs(seconds * NANOS_PER_SEC);
- uint64_t now = mach_absolute_time();
- mach_wait_until(now + time_to_wait);
- }
timer实现的更多相关文章
- C# - 计时器Timer
System.Timers.Timer 服务器计时器,允许指定在应用程序中引发事件的重复时间间隔. using System.Timers: // 在应用程序中生成定期事件 public class ...
- winform 用户控件、 动态创建添加控件、timer控件、控件联动
用户控件: 相当于自定义的一个panel 里面可以放各种其他控件,并可以在后台一下调用整个此自定义控件. 使用方法:在项目上右键.添加.用户控件,之后用户控件的编辑与普通容器控件类似.如果要在后台往窗 ...
- 【WPF】 Timer与 dispatcherTimer 在wpf中你应该用哪个?
源:Roboby 1.timer或重复生成timer事件,dispatchertimer是集成到队列中的一个时钟.2.dispatchertimer更适合在wpf中访问UI线程上的元素 3.Dispa ...
- STM32F10xxx 之 System tick Timer(SYSTICK Timer)
背景 研究STM32F10xxx定时器的时候,无意间看到了System tick Timer,于是比较深入的了解下,在此做个记录. 正文 System tick Timer是Cotex-M内核的24位 ...
- 本地数据Store。Cookie,Session,Cache的理解。Timer类主要用于定时性、周期性任务 的触发。刷新Store,Panel
本地数据Store var monthStore = Ext.create('Ext.data.Store', { storeId : 'monthStore', autoLoad : false, ...
- WinForm用户控件、动态创建添加控件、timer控件--2016年12月12日
好文要顶 关注我 收藏该文 徐淳 关注 - 1 粉丝 - 3 0 0 用户控件: 通过布局将多个控件整合为一个控件,根据自己的需要进行修改,可对用户控件内的所有控件及控件属性进行修 ...
- java Timer 定时每天凌晨1点执行任务
import java.util.TimerTask;/** * 执行内容 * @author admin_Hzw * */public class Task extends TimerTask { ...
- [C#].NET中几种Timer的使用
这篇博客将梳理一下.NET中4个Timer类,及其用法. 1. System.Threading.Timer public Timer(TimerCallback callback, object s ...
- 使用系统自带的GCD的timer倒计时模板语句遇到的小坑。。
今天折腾了下系统gcd的 但是如果不调用这句dispatch_source_cancel()那么这个timer根本不工作....解决方法如下: 实现一个倒计时用自带的gcd如此简洁.. 原因可能是如果 ...
- C# 定时器 Timers.Timer Forms.Timer
1.定义在System.Windows.Forms里 Windows.Forms里面的定时器比较简单,只要把工具箱中的Timer控件拖到窗体上,然后设置一下事件和间隔时间等属性就可以了 //启动定时器 ...
随机推荐
- python Tkinter 的 Text 保持焦点在行尾
https://bbs.csdn.net/topics/390712532 text.see(END)
- 利用RabbitMQ、MySQL实现超大用户级别的消息在/离线收发
由于RabbitMQ中只有队列(queue)才能存储信息,所以用RabbitMQ实现超大用户级别(百万计)的消息在/离线收发需要对每一个用户创建一个永久队列. 但是RabbitMQ节点内存有限,经测试 ...
- Mave环境搭建SSM集成空项目
---恢复内容开始--- 一.空项目案例 软件: 链接:https://pan.baidu.com/s/18Fk8frnWMBRho43P98C97w 提取码:0rk7 项目:链接:https://p ...
- AIDL 进程间通信的一个小小的总结
需求 项目需要,将做好的项目作为一个服务提供给另一个公司.我们需要提供一个apk,所以设计到进程间交互,不得不了解一下AIDL了. 了解一下AIDL 之前准备面试的时候,或多或少的了解了一点AIDL, ...
- 武汉Uber优步司机奖励政策(1月11日~1月17日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 【LG2495】[SDOI2011]消耗战
[LG2495][SDOI2011]消耗战 题面 洛谷 题解 参考博客 题意 给你\(n\)个点的一棵树 \(m\)个询问,每个询问给出\(k\)个点 求将这\(k\)个点与\(1\)号点断掉的最小代 ...
- 阅读笔记《JavaScript语言精粹》
阅读笔记<JavaScript语言精粹> 对象 1.检索属性 使用[]和. 2.引用传递 JavaScript的简单数据类型包括数字.字符串.布尔值.null值和undefined值.其它 ...
- javaweb(四)——Http协议(请求头,响应头详解)
一.什么是HTTP协议 HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的 ...
- katalon系列六:Katalon Studio Web UI关键字讲解
在一个Test Case里,点左上Add-Web UI Keyword,可以添加一行新的命令. 像Click.setText.Delay这些最基本的,大家还是看看官方的API文档吧,望文知义,如果是纯 ...
- 接口测试工具postman(七)下载文件接口
按照一般请求接口,配置好接口地址以及参数,点击Send and Download 按钮,执行请求的同时会下载文件