由最后的闰秒的启发,我使用POSIX调用一直在探索的定时(即,间隔计时器)。
POSIX提供了几种方法来设置定时器,但他们都存在问题:
-
sleep
和nanosleep
-这些都是烦人他们一个信号中断后重新启动,他们引进的时钟偏移。 你可以避开一些,但不是全部,这与歪斜一些额外的工作,但这些功能使用实时时钟,所以这也不是没有缺陷。 -
setitimer
或更现代的timer_settime
-这些被设计为间隔定时器,但他们每个过程,这是一个问题,如果你需要多个活动的定时器。 他们也不能使用同步,但这是少了大事。 -
clock_gettime
和clock_nanosleep
当用于看似正确答案CLOCK_MONOTONIC
。 clock_nanosleep
支持绝对超时,所以你可以睡,增加超时,并重复。 这很容易中断这种方式后,重新启动了。 不幸的是,这些功能可能会成为Linux特有:有在Mac OS X或者FreeBSD没有他们的支持。 -
pthread_cond_timedwait
可在Mac上,可以一起工作gettimeofday
的缺憾解决办法,但在Mac上只能与实时时钟工作,所以它的主题时,系统时钟设置或闰秒发生的不当行为。
是否有我丢失的API? 有没有在类UNIX系统上创建乖巧间隔定时器一个合理的可移植的方式,还是这个总结的东西今天的状态?
通过规矩,合理便携式,我的意思是:
- 不容易出现时钟偏差(负,当然,系统时钟本身的偏移)
- 弹性的以系统时钟被设置或被一个闰秒发生
- 能够支持多个计时器在同一进程
- 可至少在Linux,Mac OS X和FreeBSD
上闰秒的说明 (响应于R.,的回答 ):
POSIX天正是86400秒长,但现实世界的日子很少能长或短。 系统如何解决这种差异是实现定义的,但它是常见的闰秒共享相同的UNIX时间戳之前的第二位。 另请参见: 闰秒和什么与他们无关 。
Linux内核闰秒的错误是没有设置时钟拨回一秒钟后,以做家政的结果: https://lkml.org/lkml/2012/7/1/203 。 即使没有错误,时钟就跳下去向后一秒钟。
POSIX定时器( timer_create
)不需要的信号; 你也可以安排计时器到期时在一个线程中通过交付SIGEV_THREAD
通知类型。 不幸的glibc的实现实际上会为每个到期一个新的线程(两者有很多的开销和破坏的实时质量稳健性的希望),尽管该标准允许每个到期同一线程的重用。
简短的说,我只想建议让自己的线程使用clock_nanosleep
与TIMER_ABSTIME
和CLOCK_MONOTONIC
的间隔定时器。 既然你提到了一些破碎的系统可能缺乏这些接口,你可以简单地有一个下拉的实现(例如基于pthread_cond_timedwait
在这样的系统),并计算它可能是品质较低,由于缺乏单调时钟,但是,这是只使用低质量实行类似的MacOSX的基本限制。
至于你提到闰秒的关注,如果NTPD或类似的是使您的实时时钟向后跳的时候,一个闰秒发生,这是在NTPD一个严重的错误。 POSIX时间(从epoch秒)是在每标准日历秒(一天的准确八万六千四分之一),而不是SI秒为单位,因此,唯一的地方跨越第二逻辑所属的POSIX系统上(如果任何地方)处于mktime
/ gmtime
/ localtime
时,他们之间的转换time_t
和破旧的时间。 我一直没下袭击这一次的错误,但他们似乎是由于系统软件做了很多愚蠢的错误和东西,不是从任何根本性的问题。
kqueue
和kevent
可以用于此目的。 OSX 10.6和FreeBSD 8.1新增支持EVFILT_USER
,我们可以用它来唤醒从另一个线程的事件循环。
请注意,如果你用它来实现自己的条件和timedwait,你不需要锁,以避免竞争状态,违背了这个优秀的答案 ,因为你不能“错过”的队列中的事件。
资料来源:
- FreeBSD的手册页
- OS X man页面
- kqueue的教程
- libevent的源代码
示例代码
与编译clang -o test -std=c99 test.c
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
// arbitrary number used for the identifier property
const int NOTIFY_IDENT = 1337;
static int kq;
static void diep(const char *s) {
perror(s);
exit(EXIT_FAILURE);
}
static void *run_thread(void *arg) {
struct kevent kev;
struct kevent out_kev;
memset(&kev, 0, sizeof(kev));
kev.ident = NOTIFY_IDENT;
kev.filter = EVFILT_USER;
kev.flags = EV_ADD | EV_CLEAR;
struct timespec timeout;
timeout.tv_sec = 3;
timeout.tv_nsec = 0;
fprintf(stderr, "thread sleep\n");
if (kevent(kq, &kev, 1, &out_kev, 1, &timeout) == -1)
diep("kevent: waiting");
fprintf(stderr, "thread wakeup\n");
return NULL;
}
int main(int argc, char **argv) {
// create a new kernel event queue
kq = kqueue();
if (kq == -1)
diep("kqueue()");
fprintf(stderr, "spawn thread\n");
pthread_t thread;
if (pthread_create(&thread, NULL, run_thread, NULL))
diep("pthread_create");
if (argc > 1) {
fprintf(stderr, "sleep for 1 second\n");
sleep(1);
fprintf(stderr, "wake up thread\n");
struct kevent kev;
struct timespec timeout = { 0, 0 };
memset(&kev, 0, sizeof(kev));
kev.ident = NOTIFY_IDENT;
kev.filter = EVFILT_USER;
kev.fflags = NOTE_TRIGGER;
if (kevent(kq, &kev, 1, NULL, 0, &timeout) == -1)
diep("kevent: triggering");
} else {
fprintf(stderr, "not waking up thread, pass --wakeup to wake up thread\n");
}
pthread_join(thread, NULL);
close(kq);
return EXIT_SUCCESS;
}
产量
$ time ./test
spawn thread
not waking up thread, pass --wakeup to wake up thread
thread sleep
thread wakeup
real 0m3.010s
user 0m0.001s
sys 0m0.002s
$ time ./test --wakeup
spawn thread
sleep for 1 second
thread sleep
wake up thread
thread wakeup
real 0m1.010s
user 0m0.002s
sys 0m0.002s
你可以看一下这个问题在这里为clock_gettime
仿真,我还提供了一个答案,而是帮助我为好。 我最近增加了一个简单的定时器,以一个小仓库我一直对Mac OS X的时序是部分模拟POSIX调用。 一个简单的测试运行在2000Hz的计时器。 回购协议被称为PosixMachTiming 。 试试看。
PosixMachTiming是基于马赫 。 看来一些计时相关马赫的API已经从苹果公司的网页消失了,已经过时了,但仍有漂浮的源代码位。 它看起来像AbsoluteTime
单位和内核抽象发现这里是做事情的新方式。 反正在PosixMachTiming回购仍然为我工作。
PosixMachTiming概述
clock_gettime
被模拟为CLOCK_REALTIME
由马赫函数调用自来水进入系统实时时钟,被称为CALENDAR_CLOCK
。
所述clock_gettime
被仿真为CLOCK_MONOTONIC
通过使用全局变量( extern mach_port_t clock_port
)。 计算机可以打开或可能被唤醒时,这个时钟被初始化。 我不确定。 在任何情况下,它的全局变量,函数mach_absolute_time()
调用。
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)
通过使用模拟nanosleep
当前时间和绝对单调时间之间的差。
itimer_start()
和itimer_step()
是基于呼叫clock_nanosleep
的目标绝对单调的时间。 它通过递增在每次迭代(不是当前时间),使得时钟偏移不是问题的时间步长的目标时间。
请注意,这还不能满足你的要求是能够支持多个计时器在相同的过程。