Periodic监视器也是libev中定时器的一种,其功能很多,但实现较为复杂。与ev_timer不同,periodic监视器不是基于实时(或相对时间,即过去的物理时间),而是基于钟表时间(绝对时间,及系统钟表过去的事件)。不同的是,钟表时间可能比实时时间跑得快或慢,时间跳跃并不罕见(例如,当你调整钟表时间),通常更适合长时间的计时。
可以让periodic监视器在某个特定时间点后触发:例如,如果希望“在10秒内”触发(通过指定例如ev_now () + 10)。也就是说,绝对时间而不是延迟),然后将系统时钟重置为前一年的1月,则需要一年或更长时间来触发事件(不像ev_timer,它在启动后仍会触发大约10秒,因为它使用相对超时)。
periodic监视器也可以用来实现更复杂的计时器,比如在每个“当地时间的午夜”触发一个事件,或者其他复杂的规则。这一点ev_timer watcher很难做到,因为无法对时间跳跃做出反应。
与计时器一样,回调保证只在应该触发的时间点过去时才被调用。如果多个定时器在同一个循环迭代中准备好了,那么具有更早超时值的定时器将在具有更晚超时值的定时器之前被调用(当回调递归调用ev_run时,情况就不同了)
主要函数:
ev_periodic_init (ev_periodic *, callback, ev_tstamp offset, ev_tstamp interval, reschedule_cb)
ev_periodic_set (ev_periodic *, ev_tstamp offset, ev_tstamp interval, reschedule_cb)
很多参数,实际上有三种操作模式,我们直接介绍操作模式,将从最简单到最复杂来解释:
1)绝对时间定时器:absolute timer (offset = absolute time, interval = 0, reschedule_cb = 0)
在这种配置中,检测器在时钟时间offset过去后触发一个事件。当发生时间跳跃时,它不会重复也不会调整,也就是说,如果它在2020年1月1日运行,那么当系统时钟达到或超过该时间点+offset时,它将被停止并调用。
2)重复间隔定时器:repeating interval timer (offset = offset within interval, interval > 0, reschedule_cb = 0)
在这种模式下,监视器将总是被安排在下一个(offset + N * interval
)时间超时(N为整数,它也可以是负的),然后重复,而不管任何时间跳跃,偏移参数仅仅是间隔周期的偏移。可用于创建不随系统时钟漂移的定时器,例如,创建一个每小时触发的ev_periodic:
ev_periodic_set (&periodic, 0., 3600., 0);
这并不意味着两次触发之间总是有3600秒的间隔,而只是当系统时间显示为一个完整小时(UTC)时,或者更准确地说,当系统时间可被3600整除时,才会调用回调。另一种更偏向数学的解释是ev_periodic将在下一个满足time = offset (mod interval)的时间运行回调,而不考虑任何时间跳跃。interval必须是正的,并且为了数值稳定,interval应该大于1/8192(大约100微秒),offset应该大于0,并且最多应该具有与当前时间相似的幅度(比如说,在10倍之内)。offset的典型值实际上是0或介于0和interval之间的值,这也是推荐的范围。还要注意,定时器触发的频率是有上限的(例如,CPU速度),所以如果间隔很小,那么定时稳定性当然会下降。Libev本身试图精确到大约一毫秒(如果操作系统支持并且机器足够快)。
3)手动重安排模式: manual reschedule mode (offset ignored, interval ignored, reschedule_cb = callback)
它必须根据过去的时间值(即大于第二个参数的最低时间值)返回下一次触发的时间。它通常在回调被触发之前被调用,但也可能在其他时间被调用。 注意:这个回调必须总是返回一个大于或等于现在传递值的时间。 这可以用来创建非常复杂的计时器,例如在“下一个午夜,当地时间”触发的计时器。要做到这一点,需要计算从现在起的下一个午夜,并返回时间戳值。下面是一个关于如何做到这一点的例子:
#include <time.h>
static ev_tstamp
my_rescheduler (ev_periodic *w, ev_tstamp now)
{
time_t tnow = (time_t)now;
struct tm tm;
localtime_r (&tnow, &tm);
tm.tm_sec = tm.tm_min = tm.tm_hour = 0; // midnight current day
++tm.tm_mday; // midnight next day
return mktime (&tm);
}
最后给出一个例子很好的说明三种基本方法的使用:
#include <ev.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h> // for fmod
#define TIMEOUT 4.
struct ev_loop *loop = NULL;
ev_periodic periodic_watcher;
static void periodic_cb(struct ev_loop *loop, ev_periodic *w, int revents)
{
printf("periodic_cb() call\n");
}
static ev_tstamp scheduler_cb(ev_periodic *w, ev_tstamp now)
{
double mod = fmod(now, TIMEOUT);
printf("scheduler_cb() call, now = %lf, mod = %lf\n", now, mod);
return now + (TIMEOUT - mod);
}
void *ev_periodic_create(void *p)
{
loop = ev_loop_new(EVFLAG_AUTO);
// 下面三种初始化方法都可以,实现的效果是一样的。
//ev_periodic_init(&periodic_watcher, periodic_cb, 0., TIMEOUT, 0); // periodic_cb每隔TIMEOUT秒被调用一次,对应基本方法1.
//ev_periodic_init(&periodic_watcher, periodic_cb, fmod(ev_now(loop), TIMEOUT), TIMEOUT, 0); // periodic_cb每隔TIMEOUT秒被调用一次,对应基本方法2.
ev_periodic_init(&periodic_watcher, periodic_cb, 0., 0., scheduler_cb); // periodic_cb每隔TIMEOUT秒被调用一次,对应基本方法3.
// 需要注意的是对于第三种初始化方法,执行下面这个方法后会主动去调用一次scheduler_cb函数(但此时并不触发periodic_cb函数),以后就是每隔TIMEOUT秒后才调用scheduler_cb,并且触发periodic_cb。
ev_periodic_start(loop, &periodic_watcher);
printf("ev_periodic_create() call, after start!\n");
ev_run(loop, 0);
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, ev_periodic_create, NULL);
while(1)
{
static int count = 0;
printf("count = %d\n", count++);
sleep(1);
}
return 0;
}
其它函数:
ev_periodic_again (loop, ev_periodic *):
只需停止并重新启动定期观察器。只有当更改了一些参数,或者重新计划回调返回的时间与上次调用的时间不同时(例如,在类似crond的程序中,当crontabs发生更改时),这才是有用的。
ev_tstamp ev_periodic_at (ev_periodic *):
激活时,返回观察者下一次触发的绝对时间。这与ev_periodic_set的offset
参数不同,但实际上甚至在间隔和手动重新调度模式下也能工作。
ev_tstamp offset [read-write]:
重复时,它包含偏移值,否则这就是绝对时间点(偏移值传递给ev_periodic_set,尽管libev可能会修改此值以获得更好的数值稳定性)。 可以随时修改,但更改仅在定期计时器触发或再次调用ev _ periodic _时生效。
ev_tstamp interval [read-write]:
当前间隔值。可以随时修改,但更改仅在定期计时器触发或再次调用ev _ periodic _时生效。
ev_tstamp (*reschedule_cb)(ev_periodic *w, ev_tstamp now) [read-write]:
当前的重新计划回调,如果此功能被关闭,则为0。可以随时更改,但更改仅在定期计时器触发或再次调用ev _ periodic _时生效。