是否有任何表现良好的POSIX间隔定时器?

受到最后一个闰秒的启发,我一直在使用POSIX调用探索计时(特别是间隔计时器)。

POSIX提供了几种设置计时器的方法,但它们都有问题:

  • sleepnanosleep sleep – 这些都是在被信号中断后重新启动,并引入时钟偏差。 你可以通过一些额外的工作来避免一些但不是全部的这种偏差,但这些function使用的是实时时钟,所以这并非没有陷阱。
  • setitimer或更现代的timer_settime被设计为间隔定时器,但它们是每个进程,如果你需要多个活动定时器,这是一个问题。 它们也不能同步使用,但这不是什么大问题。
  • clock_nanosleep使用时, clock_gettimeclock_nanosleep似乎是正确的答案。 clock_nanosleep支持绝对超时,因此您可以只是睡眠,增加超时,然后重复。 在中断之后也很容易重启。 不幸的是,这些function可能也是特定于Linux的:在Mac OS X或FreeBSD上不支持它们。
  • pthread_cond_timedwait在Mac上可用,并且可以作为一个kludgy解决方案使用gettimeofday ,但在Mac上它只能与实时时钟一起使用,因此当系统时钟设置或闰秒发生时它会受到不良行为的影响。

我缺少一个API吗? 是否有一种合理的可移植方式在类UNIX系统上创建表现良好的间隔计时器,或者这总结了今天的状态?

通过良好的行为和合理的便携性,我的意思是:

  • 不容易出现时钟偏差(当然,减去系统时钟自身的偏差)
  • 适应正在设置的系统时钟或发生闰秒
  • 能够在同一个过程中支持多个计时器
  • 至少可在Linux,Mac OS X和FreeBSD上使用

关于闰秒的注释 (响应R ..的回答 ):

POSIX天的长度恰好是86,400秒,但现实世界的日子很少会更长或更短。 系统如何解决这种差异是由实现定义的,但闰秒通常与前一秒共享相同的UNIX时间戳。 另请参见: 闰秒以及如何处理它们 。

Linux内核闰第二个错误是在将时钟设置为秒后未能进行内务处理的结果: https : //lkml.org/lkml/2012/7/1/203 。 即使没有那个错误,时钟也会向后跳一秒钟。

POSIX定时器( timer_create )不需要信号; 您还可以通过SIGEV_THREAD通知类型安排在线程中传递计时器到期。 不幸的是,glibc的实现实际上为每个到期创建了一个新线程(它们都有很多开销并且破坏了实时质量鲁棒性的任何希望),尽管标准允许在每个到期时重用相同的线程。

clock_nanosleep ,我建议你制作自己的线程,使用带有TIMER_ABSTIMECLOCK_MONOTONIC作为间隔计时器。 由于您提到某些损坏的系统可能缺少这些接口,您可以在这样的系统上简单地使用插入式实现(例如基于pthread_cond_timedwait ),并且由于缺少单调时钟而可能认为它质量较低,但这是只是使用像MacOSX这样的低质量实现的基本限制。

至于你对闰秒的关注,如果ntpd或类似物使你的实时时钟在闰秒发生时向后跳跃,这是ntpd中的一个严重错误。 POSIX时间(自纪元以来的秒数)以标准的日历秒(正好是一天的1/86400)为单位,而不是SI秒,因此唯一的地方闰秒逻辑属于POSIX系统(如果有的话)在mktime / gmtime / localtimetime_t和break-down时间之间进行转换。 我没有关注这次发生的错误,但它们似乎是系统软件做了很多愚蠢和错误的事情,而不是任何根本问题。

kqueuekevent可用于此目的。 OSX 10.6和FreeBSD 8.1增加了对EVFILT_USER支持,我们可以用它来唤醒来自另一个线程的事件循环。

请注意,如果您使用它来实现自己的条件和timedwait,则不需要锁定以避免竞争条件,这与此优秀答案相反,因为您不能“错过”队列中的事件。

资料来源:

  • FreeBSD手册页
  • OS X手册页
  • kqueue教程
  • 解放源代码

示例代码

clang -o test -std=c99 test.c编译

 #include  #include  #include  #include  #include  #include  #include  #include  // 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基于Mach 。 似乎一些与时序相关的Mach API已从Apple的页面中消失并且已经弃用,但仍有一些源代码浮动。 看起来像AbsoluteTime单元和这里发现的内核抽象是新的做事方式。 无论如何, PosixMachTiming回购仍然适用于我。

PosixMachTiming概述

clock_gettime通过一个马赫函数调用为CLOCK_REALTIME模拟,该函数调用进入系统实时时钟,称为CALENDAR_CLOCK

通过使用全局变量( extern mach_port_t clock_port )为CLOCK_MONOTONIC模拟clock_gettime 。 当计算机开启或可能唤醒时,此时钟被初始化。 我不确定。 无论如何,它是函数mach_absolute_time()调用的全局变量。

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)通过使用nanosleep来模拟当前时间和绝对单调时间之间的差异。

itimer_start()itimer_step()基于调用clock_nanosleep以获得目标绝对单调时间。 它将目标时间增加每次迭代的时间步长(而不是当前时间),这样时钟偏差就不是问题。

请注意,这不能满足您在同一进程中支持多个计时器的要求。