受到最后一个闰秒的启发,我一直在探索使用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作为一个kludgy解决方案,但在Mac上它只能与实时时钟一起使用,因此当系统时钟设置或闰秒发生时,它会受到不当行为的影响.我缺少一个API吗?是否有一种合理的可移植方式在类UNIX系统上创建性能良好的间隔定时器,或者这总结了今天的状态?
通过良好的行为和合理的便携性,我的意思是:
关于闰秒的注释(响应R ..的回答):
POSIX天的长度恰好是86,400秒,但现实世界的日子很少会更长或更短.系统如何解决这种差异是由实现定义的,但闰秒通常与前一秒共享相同的UNIX时间戳.另请参见:闰秒以及如何处理它们.
Linux内核闰第二个错误是在将时钟设置为秒后无法进行内务处理的结果:https://lkml.org/lkml/2012/7/1/203.即使没有那个错误,时钟也会向后跳一秒钟.
POSIX定时器(timer_create)不需要信号; 您还可以通过SIGEV_THREAD通知类型安排在线程中传递计时器到期时间.不幸的是,glibc的实现实际上为每个到期创建了一个新线程(它们都有很多开销并且破坏了实时质量鲁棒性的任何希望),尽管标准允许在每个到期时重用相同的线程.
除此之外,我只建议制作自己的线程,使用clock_nanosleepwith TIMER_ABSTIME和CLOCK_MONOTONICfor interval计时器.既然你提到一些破碎的系统可能缺少这些接口,你可以简单地pthread_cond_timedwait在这样的系统上有一个插入式实现(例如基于),并且由于缺乏单调时钟而可能认为它质量较低,但这只是使用像MacOSX这样的低质量实现的基本限制.
至于你对闰秒的关注,如果ntpd或类似物使你的实时时钟在闰秒发生时向后跳跃,这是ntpd中的一个严重错误.POSIX时间(自纪元以来的秒数)以标准的日历秒(正好是一天的1/86400)为单位,而不是SI秒,因此唯一的地方是闰秒逻辑属于POSIX系统(如果有的话)在mktime/ gmtime/ localtime当他们之间的转换time_t和破旧的时间.我没有关注过这个时候出现的错误,但它们似乎是系统软件做了很多愚蠢和错误的事情,而不是任何根本问题.
kqueue并且kevent可以用于此目的.OSX 10.6和FreeBSD 8.1增加了对EVFILT_USER我们可以用来从另一个线程唤醒事件循环的支持.
请注意,如果您使用它来实现自己的条件和timedwait,则不需要锁定以避免竞争条件,这与此优秀答案相反,因为您不能"错过"队列中的事件.
编译 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;
}
Run Code Online (Sandbox Code Playgroud)
$ 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
Run Code Online (Sandbox Code Playgroud)