前往顾页
以后地位: 主页 > 收集编程 > Jsp实例教程 >

收集法度计时器凡是常利用啥实现?

时候:2015-12-11 21:00来源:知行网www.zhixing123.cn 编辑:麦田守望者

凡是来讲,就是操纵 select 的空余时候,来进行时钟查抄,非论是 select / poll / epoll/ kevent,以下统称 select,它有一个等候时候作为参数,即没有事件时,最多 wait 多少时候,我们把这个作为收集库的基准频次,比如 10MS,或 20MS, 25MS, 50MS,都是常常利用的几个值。

就是说收集库调用 select 等候事件时如果没有事件,那么最长等候 10MS 就前往了,这时候再措置完所有收集事件后,便可以来措置时钟数据了。事件措置函数就是如许:

def update_events(milisec = 10):
result = selector.select(milisec)
for fd, event in result:
do something with socket event
current = time.time()
update_timer(current)

while 1:
WAIT_MILLISEC = 10
update_events(WAIT_MILLISEC)

关头就是这个两次 select 中间 update_timer 的任务:调集合查抄需求唤醒的时钟,并且调用它们的回调函数,来驱动全部办事器的时钟运行,以最简朴的扫描法为例:

def update_timer (current):
for timer in available_timers:
while current >= timer.expires:
timer.callback(current)
timer.expires += timer.period

available_timers 记录着以后可用的所有 timer 的调集,expires 是他们需求被触发的时候,如果以后时候年夜于即是这个 expires,以为该 timer 需求被触发到。重视 timer.expires 更新的时候是 += 周期,而不是 = current + 周期,后者会导致偏差堆集,长时候运行后偏差愈来愈年夜。同时这里需求 while,因为可能超越两个以上周期,当然只运行一次的 timer 就不需求了,这里只是简化下。

比如 libevent 内里的主循环 event_base_loop 每次 select 结束后就调用一次 timeout_process。

这就是 Timer 调剂的基来源根底理。

可能你会发明每次 select 结束都要扫描全部 available_timers 调集,是一个非常费时候的事情,那么起首想到的就是优先队列了:将 Timer 节点遵循 expires 的前后依次,将最将近产生的超时节点放在前面,每次检测队列头便可以判定是不是超时了。


比如 libevent 内里的 timerout_process 函数,就是用最小堆来存储超时势务,每次检测堆的第一个节点如果超时则删除并继续检测下一个,不然跳出循环(此时没有任何节点到期)。

另有一种牢固超时队列,就是内里的节点的超时周期都是不异的,那么每次增加都在最后,每次检测都只检测头部。比如所有链接都要检测60秒无事件超时这个事情,便可以用它,因为60秒是牢固的,新增时放到队列最后,检测时只检测头部是不是超时,如果有事件离开,就删除并从头插手队列开端,这是牢固超时队列。

另有下面特地说的体系供应的 timerfd,建立后插手 select, 但是受限于 linux 体系,跨平台就用不了了,不克不及太依靠。

但是这些都不算最完美的处理计划,一旦超时节点多达上万个,每个时候都不合,又考虑通用实现(非特定平台实现)的话,这几种调剂体例是要亏损的,目前最好的算法是 Linux Kernel 的时候轮算法,几近包管不管有多少个时钟工具要措置,每次 update_timer 的时候都几近是常数。

详细可以看代码 kernel/timer.c ,时候轮的利用层实现见我写的:
AsyncNet/itimer.h at master · skywind3000/AsyncNet · GitHub
AsyncNet/itimer.c at master · skywind3000/AsyncNet · GitHub

两个文件,拷贝走就得了,没有任何第三个文件的依靠,更没有全局独一的时钟办理器。

一般来讲 “侵入式” 的收集库,都会把这个事情给办理了,因为他们领受你的主循环,比如 libevent, skynet 之类,你进入一个 event_loop 就只需法度结束才气出来了,而非侵入式的收集库不会领受你的主循环,可让你本身主动去触发。

比如某同事想在 libevent 上跑一套本身的时钟体系,而 event_base_dispatch 属于出来就出不来的 “侵入式” 设想。为了能在 select 前后插手本身的时钟调剂,不克不及不把 libevent 改出一个 branch 来,所以 侵入式 是比较卑劣的设想。libevent 应当学习一下 win32,把 GetMessage, DispatchMessage 放出来,比如叫做:

int event_base_update(struct event_base *base, int max_wait_time);

让你主动去触发它,然后矫捷的在前后做点事情,还可以用 event_base_wakeup 从别的一个现成唤醒它,如许就会矫捷很多了,完整代替 event_base_dispatch 这个出来出不来的死循环。

每次 select wait 的时候一般用一个牢固值,称为一个 TICK,牢固值选年夜了,时钟基准周期就会很长,短时偏差就会增年夜,选小了,又会占用分外 cpu,可以摹拟 Linux 利用 100Hz的值,即 10ms来做,这也是通行做法。

不嫌费事还可以每次从 timer 调集内里挑选最早要超时的事件,计较另有多长时候就会超时,作为 select wait 的值,每次都不一样,每次都根基切确,同时不会占用多余 cpu,这叫 tickless,Linux 的 3.x以上版本也支撑 tickless 的形式来驱动各种体系级时钟,号称更省电更切确,不过需求你手动翻开,FreeBSD 9 今后也引入了 tickless。

TICKLESS 形式可以说是一个新的标的目标,但是目前处于默许封闭的测试状况,那么你的收集库究竟是用 TICK 还是 TICKLESS,看你按照详细环境来评价了。

顶一下
(0)
0%
踩一下
(0)
0%
------分开线----------------------------
标签(Tag):收集法度计时器
------分开线----------------------------
颁发评论
请自发遵循互联网相关的政策法规,严禁公布色情、暴力、革命的谈吐。
评价:
神色:
考证码:点击我更换图片
猜你感兴趣