20

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

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

  • sleep并且nanosleep——它们在被信号中断后重新启动很烦人,并且它们会引入时钟偏差。您可以通过一些额外的工作来避免部分但不是全部的这种偏差,但这些功能使用实时时钟,所以这并非没有缺陷。
  • setitimer或更现代的timer_settime——这些被设计为间隔计时器,但它们是每个进程的,如果您需要多个活动计时器,这是一个问题。它们也不能同步使用,但这没什么大不了的。
  • clock_gettime与.clock_nanosleep一起使用时似乎是正确的答案CLOCK_MONOTONICclock_nanosleep支持绝对超时,所以你可以只睡觉,增加超时,然后重复。以这种方式中断后重新启动也很容易。不幸的是,这些函数也可能是特定于 Linux 的:Mac OS X 或 FreeBSD 上不支持它们。
  • pthread_cond_timedwait在 Mac 上可用,可以gettimeofday作为一种笨拙的解决方法使用,但在 Mac 上,它只能与实时时钟一起使用,因此当设置系统时钟或发生闰秒时,它会出现异常行为。

有没有我缺少的 API?是否有一种合理的可移植方式在类 UNIX 系统上创建行为良好的间隔计时器,或者这是否总结了当今的事物状态?

我的意思是表现良好且可合理携带:

  • 不易出现时钟偏差(当然,减去系统时钟自身的偏差)
  • 对正在设置的系统时钟或闰秒的发生具有弹性
  • 能够在同一进程中支持多个定时器
  • 至少在 Linux、Mac OS X 和 FreeBSD 上可用

关于闰秒的注释(响应R..'s answer):

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

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

4

4 回答 4

6

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

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

资料来源:

示例代码

编译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
于 2015-07-02T02:42:22.577 回答
5

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

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

至于你对闰秒的担忧,如果 ntpd 或类似的东西在闰秒发生时让你的实时时钟向后跳,那是 ntpd 中的一个严重错误。POSIX 时间(自纪元以来的秒数)按照标准以日历秒为单位(恰好是一天的 1/86400),而不是 SI 秒,因此闰秒逻辑属于 POSIX 系统(如果有的话)的唯一位置在mktime//当它们之间转换gmtime和分解的时候。我没有关注这次出现的错误,但它们似乎是由于系统软件做了很多愚蠢和错误的事情,而不是任何根本问题。localtimetime_t

于 2012-07-05T11:36:04.487 回答
1

您可以在此处查看问题以进行clock_gettime仿真,我也为此提供了答案,但也对我有所帮助。我最近在一个小存储库中添加了一个简单的计时器,用于部分模拟 POSIX 调用的 Mac OS X 计时。一个简单的测试以 2000Hz 运行计时器。该存储库称为PosixMachTiming。试试看。

PosixMachTiming 基于Mach。似乎一些与时间相关的 Mach API 已经从 Apple 的页面中消失并被弃用,但仍然有一些源代码漂浮在周围。看起来这里找到的AbsoluteTime单元和内核抽象是新的做事方式。无论如何,PosixMachTiming回购仍然对我有用。

PosixMachTiming 概述

clock_gettimeCLOCK_REALTIME由接入系统实时时钟的 mach 函数调用模拟,称为CALENDAR_CLOCK.

通过使用全局变量 ( )clock_gettime来模拟。此时钟在计算机打开或唤醒时初始化。我不确定。在任何情况下,它都是函数调用的全局变量。CLOCK_MONOTONICextern mach_port_t clock_portmach_absolute_time()

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

itimer_start()并且itimer_step()基于要求clock_nanosleep目标绝对单调时间。它在每次迭代(而不是当前时间)时按时间步长增加目标时间,因此时钟偏差不是问题。

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

于 2016-04-26T19:33:10.283 回答
-1

我们可以使用timer_create ()or timerfd_create ()。他们的例子出现在手册页中。

于 2020-01-20T13:54:51.480 回答