1

好的,所以我有一个线程,我需要每 10 毫秒运行一次,但它需要可变的处理时间(为简单起见,我们可以假设处理时间小于 10 毫秒)。随着时间的推移,时间上的微小偏差会累积起来并成为一个问题。

这是我目前的解决方案。它看起来很笨重,但我更担心运行 timeval_subtract 会导致我的时间被关闭。有没有人有更好的解决方案?

这是一个图书馆,我不能使用像计时器或时钟这样的系统资源。

void mythread(void *ptr )
{
    struct timeval tv_being, tv_end;
    int usToSleep;

    while(1)
    {
        gettimeofday(&tv_begin)

        //do stuff

        //now determine how long to sleep so we wake up 10ms after previous wakeup

        gettimeofday(&tv_end)

        usToSleep = timeval_subtract(tv_begin, tv_end); //this will return 10ms minus the elapsed time

        usleep(usToSleep);
    }

    return;
}
4

6 回答 6

3

您的方法会随着时间的推移累积错误 - 例如,如果睡眠一次运行 1 毫秒,那么您将永远无法赶上。结果将是,在很长一段时间内,您运行循环的次数将少于每 10 毫秒运行一次的次数。

为避免这种情况,请预先调用一次 time 函数,然后据此计算未来的截止日期。clock_gettime()与时钟一起使用CLOCK_MONOTONIC优于gettimeofday(),因为后者是实时时钟,因此当管理员更改系统时间时会受到影响。

例如:

#include <time.h>
#include <errno.h>

void mythread(void *ptr )
{
    struct timespec deadline;

    clock_gettime(CLOCK_MONOTONIC, &deadline);

    while(1)
    {
        //do stuff

        /* Add 10ms to previous deadline */
        deadline.tv_nsec += 10000000;
        deadline.tv_sec += deadline.tv_nsec / 1000000000;
        deadline.tv_nsec %= 1000000000;
        /* Sleep until new deadline */
        while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL) != 0)
            if (errno != EINTR) return;
    }

    return;
}

(在 2.17 之前的 glibc 版本上,您需要链接-lrt到以使用 POSIX 时钟功能)。

于 2013-05-21T22:53:51.473 回答
1

您受制于您正在使用的进程调度程序的粒度。10ms 可能是可以实现的,但请记住,在睡眠时操作系统不会在可以唤醒时立即安排它。如果其他进程领先于它,则可能会选择运行它们,因此您可能会被延迟。

你的方法是你能得到的一个好的(或一样好的)近似值。

如果您需要更好的调度,您可以考虑编译启用实时选项的 Linux 内核,这将为您提供更精细的调度粒度。

于 2013-05-21T21:29:51.980 回答
1

我将使用由基于时钟的计时器触发的实时信号( SIGRTMIN+0to )和发布全局信号量的信号处理程序。SIGRTMAXCLOCK_MONOTONIC

sem_post()是异步信号安全的,可以在信号处理程序中可靠地使用。这是根据 POSIX.1-2008 的,并且可以与 POSIX.1-1990 兼容;因此,这应该可以在所有操作系统上正常工作(Windows 除外,像往常一样)。

定时函数本身调用

    while (sem_wait(&semaphore) == -1 && errno == EINTR)
        ;

或者

    while (!sem_trywait(&semaphore))
        skipped++;

    while (sem_wait(&semaphore) == -1 && errno == EINTR)
        ;

等待下一个刻度发生。

(信号传递将中断sem_wait()调用,除非信号处理程序安装时SA_RESTART设置了标志。SA_RESTART为进程中所有已安装的信号处理程序设置,sem_wait(&semaphore);单独使用就足够了。)

这就是我个人更喜欢这种方法的原因:

  • 内核(或 C 库或线程库)维护间隔。这样我就不需要调用gettimeofday()or clock_gettime(),或者计算适当的睡眠时间。

  • 即使在较高的 CPU 负载下,增加进程的优先级也会产生较小的抖动。

  • 我可以使用具有多个计时器和不同间隔的单个信号处理程序。当计时器事件是一个信号时,信号处理程序将在siginfo->si_value(and siginfo->si_code == SI_TIMER) 中获得一个特定于计时器的值。

  • 线程池,甚至是动态调整大小的线程池,创建起来都很简单。池中的每个线程只需调用sem_wait(), 以在下一个间隔滴答时触发。

  • 可以很容易地检测到溢出 ( timer_getoverruns())。

  • 滴答声被排队,多个滴答声(“丢失的滴答声”)可以很容易地出列。(while (sem_trywait(&semaphore) == 0) dequeued++;在等待信号量之前使用。)

  • 在多线程进程中,内核可以使用任何进程的线程来传递信号。在多线程进程中,内核很可能会找到一个空闲线程,它可以用来立即传递信号,而不是推迟它。(我的理解;未从内核源代码验证!)

但是请注意,I/O 延迟(高 I/O 或使大型设备变慢的 I/O)可能会导致大(偶尔)抖动。然而,这对于任何计时方法都是正确的。

这是一个示例程序,jitter.c

/* This is POSIX C. strsignal() is in 200809L, otherwise 199309L is okay. */
#define _POSIX_C_SOURCE 200809L

#include <signal.h>
#include <time.h>
#include <errno.h>
#include <semaphore.h>

#include <string.h>
#include <stdio.h>

static volatile sig_atomic_t    interrupted = 0;

/* Interrupt handler. Just updates the above variable to match the signal number.
*/
static void interrupt_handler(int signum)
{
    interrupted = signum;
}

/* Install interrupt handler.
*/
static int interrupt_on(const int signum)
{
    struct sigaction    act;

    if (signum < 1 || signum > SIGRTMAX)
        return errno = EINVAL;

    sigemptyset(&act.sa_mask);
    act.sa_handler = interrupt_handler;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL))
        return errno;

    return 0;
}

static timer_t                  periodic_timer;
static struct itimerspec        periodic_interval;
static int                      periodic_signal = -1; /* Not installed */
static sem_t                    periodic_tick;

/* Periodic tick handler. Just posts the semaphore.
 * Note: sem_post() is async-signal-safe.
*/
static void periodic_signal_handler(int signum)
{
    if (signum == periodic_signal)
        sem_post(&periodic_tick);
}

/* Install periodic tick. Returns 0 if success, errno error code otherwise.
*/
static int periodic_start(const int signum, const double interval_seconds)
{
    struct sigaction    act;
    struct sigevent     event;

    /* Invalid signal number? Invalid interval? */
    if (signum < 1 || signum > SIGRTMAX || interval_seconds <= 0.0)
        return errno = EINVAL;

    /* Verify there is no periodic signal yet. */
    if (periodic_signal != -1)
        return errno = EINVAL;

    /* Initialize the semaphore. */
    if (sem_init(&periodic_tick, 0, 0))
        return errno;

    /* Define interval. */
    {
        long    s  = (long)interval_seconds;
        long    ns = (long)(1000000000.0 * (interval_seconds - (double)s));

        /* Overflow in seconds? */
        if (s < 0L)
            return errno = EINVAL;

        /* Make sure ns is within limits. */
        if (ns < 0L)
            ns = 0L;
        else if (ns > 999999999L)
            ns = 999999999L;

        /* Zero seconds maps to one nanosecond. */
        if (s == 0L && ns == 0L)
            ns = 1L;

        periodic_interval.it_interval.tv_sec = (time_t)s;
        periodic_interval.it_interval.tv_nsec = ns;
        periodic_interval.it_value = periodic_interval.it_interval;
    }

    /* Install signal handler. */
    sigemptyset(&act.sa_mask);
    act.sa_handler = periodic_signal_handler;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    /* Describe the periodic event: it is a signal. */
    event.sigev_notify = SIGEV_SIGNAL;
    event.sigev_signo = signum;
    event.sigev_value.sival_ptr = NULL;

    if (timer_create(CLOCK_MONOTONIC, &event, &periodic_timer) == -1) {
        const int saved_errno = errno;

        /* Uninstall the signal handler. */
        act.sa_handler = SIG_DFL;
        act.sa_flags = 0;
        sigaction(signum, &act, NULL);

        /* Failed. */
        return errno = saved_errno;
    }

    /* Arm the timer. */
    if (timer_settime(periodic_timer, 0, &periodic_interval, NULL) == -1) {
        const int saved_errno = errno;

        /* Destroy the timer. */
        timer_delete(periodic_timer);

        /* Uninstall the signal handler. */
        act.sa_handler = SIG_DFL;
        act.sa_flags = 0;
        sigaction(signum, &act, NULL);

        /* Failed. */
        return errno = saved_errno;
    }

    /* Clear the overrun count. */
    timer_getoverrun(periodic_timer);

    /* Done. */
    periodic_signal = signum;
    return 0;
}

/* Uninstall periodic tick. Returns 0 if success, errno error code otherwise.
*/
static int periodic_stop(void)
{
    sigset_t                set, oldset;
    struct sigaction        action;
    const struct timespec   zerotimeout = { 0L, 0L };
    const int               signum = periodic_signal;

    /* Not installed? */
    if (signum == -1)
        return 0;

    /* Mark signal uninstalled. */
    periodic_signal = -1;

    /* Cancel the timer. This also disarms its interrupt. */
    timer_delete(periodic_timer);

    /* Create a signal set containing only the periodic signal. */
    if (sigemptyset(&set) || sigaddset(&set, signum))
        return errno;

    /* Block the periodic signal. */
    if (sigprocmask(SIG_BLOCK, &set, &oldset))
        return errno;

    /* Uninstall the signal handler. */
    sigemptyset(&action.sa_mask);
    action.sa_handler = SIG_DFL;
    action.sa_flags = 0;
    if (sigaction(signum, &action, NULL)) {
        const int saved_errno = errno;
        sigprocmask(SIG_SETMASK, &oldset, NULL);
        return errno = saved_errno;
    }

    /* Dequeue all periodic signal interrupts. */
    while (sigtimedwait(&set, NULL, &zerotimeout) == signum) {
        /* Intentionally empty */
    }

    /* Restore the signal mask. */
    if (sigprocmask(SIG_SETMASK, &oldset, NULL))
        return errno;

    /* Success. */
    return 0;
}


int main(int argc, char *argv[])
{
    double          interval, output, duration, minduration, maxduration;
    unsigned long   limit, count = 0UL, skipped;
    struct timespec prev, curr;
    char            dummy;

    if (interrupt_on(SIGINT) || interrupt_on(SIGHUP) || interrupt_on(SIGTERM)) {
        fprintf(stderr, "Cannot set interrupt handlers: %s.\n", strerror(errno));
        return 1;
    }

    if (argc < 2 || argc > 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s interval [ count ]\n", argv[0]);
        fprintf(stderr, "This program tests the timer interrupt jitter using semaphore wakeups.\n");
        fprintf(stderr, "Interval is in seconds. The program will exit after count intervals.\n");
        fprintf(stderr, "You can also interrupt the program with an INT (Ctrl-C), HUP, or TERM signal.\n");
        fprintf(stderr, "\n");
        return 0;
    }

    if (sscanf(argv[1], " %lf %c", &interval, &dummy) != 1) {
        fprintf(stderr, "%s: Invalid interval in seconds.\n", argv[1]);
        return 1;
    } else
    if (interval <= 0.0) {
        fprintf(stderr, "%s: Interval must be positive!\n", argv[1]);
        return 1;
    }

    if (argc > 2) {
        if (sscanf(argv[2], " %lu %c", &limit, &dummy) != 1) {
            fprintf(stderr, "%s: Invalid number of interrupts.\n", argv[2]);
            return 1;
        }
    } else
        limit = ~0UL;

    if (periodic_start(SIGRTMIN+0, interval)) {
        fprintf(stderr, "Cannot set up a periodic interrupt: %s.\n", strerror(errno));
        return 1;
    }

    clock_gettime(CLOCK_REALTIME, &curr);
    minduration = maxduration = interval;
    output = 0.0;
    skipped = 0UL;

    printf("Interval is %lu.%09ld seconds.\n",
           (unsigned long)periodic_interval.it_interval.tv_sec, periodic_interval.it_interval.tv_nsec);
    fflush(stdout);

    while (count++ < limit && !interrupted) {
        while (!sem_trywait(&periodic_tick))
            skipped++;

        /* Wait for next tick. */
        prev = curr;
        while (sem_wait(&periodic_tick) == -1 && errno == EINTR);
        clock_gettime(CLOCK_REALTIME, &curr);

        duration = difftime(curr.tv_sec, prev.tv_sec) + ((double)curr.tv_nsec - (double)prev.tv_nsec) / 1000000000.0;
        if (duration < minduration) minduration = duration;
        if (duration > maxduration) maxduration = duration;

        output += duration;
        if (output >= 5.0) {
            printf("Jitter: %+9.06f .. %+9.06f milliseconds, skipped %lu ticks\n",
                   (minduration - interval) * 1000.0,
                   (maxduration - interval) * 1000.0,
                   skipped);
            fflush(stdout);

            minduration = maxduration = duration;
            output = 0.0;
            skipped = 0UL;
        }
    }

    if (output > 0.0)
        printf("Jitter: %+9.06f .. %+9.06f milliseconds, skipped %lu ticks\n",
               (minduration - interval) * 1000.0,
               (maxduration - interval) * 1000.0,
               skipped);
    fflush(stdout);

    periodic_stop();

    if (interrupted)
        fprintf(stderr, "%s.\n", strsignal(interrupted));
    else
        fprintf(stderr, "Completed.\n");

    return 0;
}

使用例如编译它

gcc -W -Wall -O3 jitter.c -lrt -o jitter

并在没有参数的情况下运行以查看使用情况。它将每五秒输出一次抖动报告。我的测试结果是通过运行获得的

./jitter 0.010

在我工作站的一个窗口中,做其他事情,然后查看输出。

在 AMD Athlon(tm) II x4 640 四核处理器上的库存 64 位 Ubuntu 3.5.0-30 通用内核上,在低到中等负载时,典型抖动小于 ±0.05 毫秒 (±50 µs),偶尔±0.20 毫秒 (±200 µs) 处的峰值。在高负载下,抖动可能达到一毫秒。

于 2013-05-22T00:54:47.813 回答
0

gettimeofday()在存储时间的线程开始时使用

gettimeofday()在线程末尾使用

计算差异 = 只是一个估计

gettimeofday()给你时间到微秒,这是你可以得到的紧

基本用法:

void millitime(){/*secondsSinceEpoch*/
    struct timeval tv;

    gettimeofday(&tv, NULL);

    printf("secs:%Ld usecs:%Ld \n",(unsigned long long)(tv.tv_sec),(unsigned long long)(tv.tv_usec));
}
于 2013-05-21T21:27:25.553 回答
0

人得到时间

符合

POSIX.1-2008 将 gettimeofday() 标记为已过时,建议改用 clock_get-time(2)。

此外,您可能希望多次运行测试并保持平均经过时间更准确。

于 2013-05-21T21:28:00.730 回答
0

http://widefox.pbworks.com/w/page/8042322/Scheduler

在消费者操作系统上获得低抖动的 10 毫秒进程是非常困难的。操作系统倾向于以该大小或更大的单位对其工作进行时间切片。如果计算机选择使用 10ms 时间片,并且有 3 个竞争线程,您可能会延迟 30ms 或更多!

如果您确实需要每 10 毫秒可靠地运行一次(周围有一些 jtiter),则必须使用实时操作系统。他们的全部目的是解决您正在解决的问题

于 2013-09-07T20:10:03.770 回答