Што гэта лепшы спосаб, каб атрымаць герметычныя тэрміны ніткі ў с (Pthreads)

Добра, такім чынам у мяне ёсць нітка, што мне трэба запусціць менавіта кожныя 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;
}
1
Ці падтрымлівае ваша сістэма рэальныя палітыкі планавання ў рэальным часе?
дададзена аўтар pilcrow, крыніца

6 адказы

Ваш падыход будзе назапашваць памылкі з цягам часу - напрыклад, калі сон бяжыць даўжыню 1мса адзін раз, то вы ніколі не злавіць, што назад. Вынікам будзе тое, што на працягу доўгага перыяду часу, вы будзеце працаваць ваш цыкл менш часу, чым калі б ён пабег адзін раз кожныя 10 мс.

Каб пазбегнуць гэтага, выклічце функцыю часу адзін раз да фронту, а затым разлічыць будучыя тэрміны, заснаваныя на гэтым. Выкарыстанне clock_gettime() з CLOCK_MONOTONIC гадзіны пераважней gettimeofday() , паколькі апошні гадзіны рэальнага часу і таму ўплывае, калі адміністратар змены сістэмнага часу.

Напрыклад:

#include 
#include 

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;
}

(У версіях Glibc да 2,17, вам неабходна звязаць з -lrt , каб выкарыстоўваць функцыі POSIX гадзін).

3
дададзена
Гэта рашэнне здаецца так проста і прама наперад. Ці існуюць якія-небудзь перавагі, выкарыстоўваючы больш складаныя рашэнні з timerfd або сігналаў замест кода? Каб быць больш канкрэтным, я шукаю рашэнне для імітацыі прайгравання гукавога файла - Мне трэба, каб падтрымліваць надзейную пазіцыю прайгравання з мінімальным назапашваннем памылак, як разумна магчыма на агульнай, сістэмы, заснаванай не ў рэальным часе Linux Debian. Акрамя таго, я не зусім разумею, што ERRNO ўмова - яна не павінна выйсці, калі перапыненне атрымана замест таго, калі гэта не так?
дададзена аўтар JustAMartin, крыніца
@JustAMartin: Сігналы даволі цяжка працаваць правільна. Перавага timerfd з'яўляецца тое, што вы можаце інтэграваць яго ў цыкл апрацоўкі падзей на аснове дэскрыптара файла, а не з дапамогай ніткі. Гэта рашэнне не павінна назапашваць якія-небудзь памылкі, вы проста атрымаць джиттера. Ўмова ERRNO з'яўляецца паўторна ўвесці сон, калі ён вярнуўся рана з-за перапынення па сігналу, і выйсці на якой-небудзь іншай памылкі (таму што любая іншая памылка з'яўляецца нечаканым і паказвае на лагічную памылку ў праграме).
дададзена аўтар caf, крыніца
OMG гэта рашэнне так проста і эфектыўнае .... я выкарыстаў, каб мець справу з гадзінамі рэальнага часу, сігналамі і семафорамі, але ваш код 10x эфектыўна! выкарыстанне працэсара ў маім дадатку (маючы патрэбу клешч кожныя 0,5 мс) упаў з 10% да 1%!
дададзена аўтар Davide Berra, крыніца

Вы знаходзіцеся на ласку зярністасці працэсу планавальніка, які вы выкарыстоўваеце. 10мс, верагодна, дасягальна, але памятайце, падчас сну, што аперацыйная сістэма не будзе планаваць яго неадкладна, калі яна даступная будзіць. Калі іншыя працэсы наперадзе яго яны могуць быць абраныя для запуску замест так што вы можаце быць адкладзеныя.

Ваш падыход з'яўляецца добрым (ці добра) у набліжэнні, як вы можаце атрымаць.

Калі вам патрабуецца больш планавання вы можаце паглядзець у кампіляцыі ядра Linux з параметрамі ў рэальным часе з падтрымкай, якая дасць вам планавання драбней зярністасць.

1
дададзена
@BrandonYates Тады гэта так жа добра, як вы можаце атрымаць з пункту гледжання набліжэння, я б на самой справе зрабіць прачнуцца час крыху менш, чым дакладнае адрозненне, але перерегулирования яго мікрасекунды або так не прыкметна.
дададзена аўтар Jesus Ramos, крыніца
Дзякуй за пацверджанне таго, што мой шлях добра. Я строй бібліятэкі, якая павінна працаваць праз любую платформу і ядра нашых кліентаў, здараюцца, выкарыстоўваючы з мінімальнымі зменамі, таму наладамі ядра або выкарыстаннем рэсурсаў на ўзровень сістэмы не варыянт.
дададзена аўтар Brandon Yates, крыніца

Я хацеў бы выкарыстаць сігнал у рэальным часе ( SIGRTMIN + 0 у SIGRTMAX ), абстраляны таймерам на аснове CLOCK_MONOTONIC гадзіны, і апрацоўшчык сігналу што вывешвае глабальны семафор.

sem_post() is async-signal-safe and can be reliably used in a signal handler. This is accабоding to POSIX.1-2008, and can be made POSIX.1-1990 compatible; therefабоe, this should wабоk fine on all operating systems (except fабо Windows, as usual).

Самі па часе выклікаў функцый

    while (sem_wait(&semaphабоe) == -1 && errno == EINTR)
        ;

або

    while (!sem_trywait(&semaphабоe))
        skipped++;

    while (sem_wait(&semaphабоe) == -1 && errno == EINTR)
        ;

to wait fабо the next tick to occur.

(The signal delivery will interrupt the sem_wait() call, unless the signal handler was installed with flag SA_RESTART set. With SA_RESTART set fабо all installed signal handlers in the process, sem_wait(&semaphабоe); alone would be enough.)

Вось чаму я асабіста аддаю перавагу гэты метад:

  • Kernel (або C library, або threading library) maintains the interval. This way I don't need to call gettimeofday() або clock_gettime(), або calculate the proper duration fабо the sleep.

  • Increasing the priабоity of the process gives smaller jitters even at higher CPU loads.

  • I can use a single signal handler with multiple timers and different intervals. When the timer event is a signal, the signal handler will get a timer-specific value in siginfo->si_value (and siginfo->si_code == SI_TIMER).

  • Thread pools, even dynamically resized thread pools, are trivial to create. Each thread in the pool simply calls sem_wait(), to trigger on the next interval tick.

  • Overruns can be detected (timer_getoverruns()) easily.

  • Ticks are queued, and multiple ticks ("lost ticks") can be trivially dequeued. (Use while (sem_trywait(&semaphабоe) == 0) dequeued++; befабоe waiting on a semaphабоe.)

  • In a multithreaded process, the kernel can use any of the process' threads to deliver the signal. In a multithreaded process it is likely that the kernel can find an idle thread it can use to deliver the signal immediately, instead of postponing it. (My understanding; not verified from kernel source!)

Note, however, that I/O latencies (high I/O або I/O to slow large devices) may cause large (occasional) jitter. This is however true fабо any timing method.

Ніжэй прыведзены прыклад праграмы, jitter.c :

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

#include 
#include 
#include 
#include 

#include 
#include 

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 semaphабоe.
 * 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 errабо 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 semaphабоe. */
    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 errабо 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 */
    }

    /* Restабоe 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", strerrабо(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 semaphабоe 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, або 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", strerrабо(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 fабо 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

and run without parameters to see the usage. It will output a jitter repабоt every five seconds. My test results were obtained by running

./jitter 0.010

in one window on my wабоkstation, doing other stuff, and looking at the output.

On a stock 64-bit Ubuntu 3.5.0-30-generic kernel on an AMD Athlon(tm) II x4 640 four-cабоe processабо, typical jitter is less than ±0.05 milliseconds (±50 µs) at low to medium loads, occasional peaks at ±0.20 milliseconds (±200 µs). At high loads, the jitter may reach a millisecond.

1
дададзена

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

Гэта вельмі цяжка атрымаць працэс 10мс нізкага джиттера на спажыўца АС. OS мае тэндэнцыю да Квант часу яго працы ў адзінках гэтага памеру або больш. Калі кампутар выбірае выкарыстоўваць 10мс квант часу, і ёсць тры канкуруючыя тэмы, вы можаце быць адкладзена на 30мс ці больш!

Калі вы на самой справе трэба надзейна працаваць кожныя 10 мс (з некаторай jtiter звязанай вакол гэтага), вы <�моцны> павінен выкарыстоўваць сістэму opeating ў рэжыме рэальнага часу. Усе іх мэта складаецца ў тым, каб вырашыць праблему, якую вы бярэцеся

0
дададзена

чалавек gettimeofday

<�Р> Сумяшчальныя </р>      <�Р> POSIX.1-2008 знакі gettimeofday() як састарэлыя, рэкамендацыі выкарыстання clock_get часу (2) замест. </Р>

Акрамя таго, вы можаце запусціць тэст некалькі разоў і захаваць сярэдні час, якое прайшло, каб быць больш дакладным.

0
дададзена
што прыемна ведаць
дададзена аўтар mf_, крыніца

выкарыстоўваць gettimeofday() пры запуску ніткі, які захоўвае час

выкарыстоўвайце gettimeofday() у канцы разьбы

вылічыць рознасць = толькі адзнака

gettimeofday() gives you time to microseconds, that is the tight you can get

basic usage:

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));
}
0
дададзена