temporizador tabata para intervalos hiit hacer ejercicio cronometro comandos cluster c timer posix

tabata - ¿Hay temporizadores de intervalo POSIX de buen comportamiento?



temporizador de intervalos para pc (3)

Los temporizadores POSIX ( timer_create ) no requieren señales; también puede hacer que la caducidad del temporizador se entregue en un hilo a través del tipo de notificación SIGEV_THREAD . Desafortunadamente, la implementación de glibc en realidad crea un nuevo subproceso para cada vencimiento (lo que conlleva muchos gastos generales y destruye cualquier esperanza de robustez de calidad en tiempo real) a pesar del hecho de que el estándar permite la reutilización del mismo subproceso para cada vencimiento.

Aparte de eso, solo recomendaría hacer tu propio hilo que use clock_nanosleep con TIMER_ABSTIME y CLOCK_MONOTONIC para un temporizador de intervalos. Ya que mencionó que algunos sistemas rotos pueden carecer de estas interfaces, simplemente podría tener una implementación pthread_cond_timedwait (basada, por ejemplo, en pthread_cond_timedwait ) en dichos sistemas, y suponer que podría ser de menor calidad debido a la falta de reloj monotónico, pero esto es Solo una limitación fundamental de usar una implementación de baja calidad como MacOSX.

En cuanto a su preocupación sobre los segundos de salto, si ntpd o similar está haciendo que su reloj en tiempo real salte hacia atrás cuando ocurre un segundo de salto, ese es un error grave en ntpd. El tiempo POSIX (segundos desde la época) está en unidades de segundos del calendario (exactamente 1/86400 de un día) por el estándar, no segundos SI, y por lo tanto, la única lógica de segundo lugar del lugar pertenece a un sistema POSIX (si corresponde) está en mktime / mktime / gmtime cuando se convierten entre time_t y time down-down. No he estado siguiendo los errores que afectaron esta vez, pero parece que se debieron a que el software del sistema hizo un montón de cosas estúpidas e incorrectas, no de un problema fundamental.

Inspirado por el último salto de segundo, he estado explorando el tiempo (específicamente, temporizadores de intervalo) usando llamadas POSIX.

POSIX proporciona varias formas de configurar los temporizadores, pero todos son problemáticos:

  • sleep y nanosleep estos son molestos para reiniciar después de ser interrumpidos por una señal, y presentan un sesgo de reloj. Puede evitar algunos, pero no todos, de este sesgo con un poco de trabajo adicional, pero estas funciones usan el reloj en tiempo real, por lo que esto no es sin escollos.
  • setitimer o el setitimer más moderno timer_settime están diseñados para ser temporizadores de intervalo, pero son por proceso, lo cual es un problema si necesita varios temporizadores activos. Tampoco se pueden usar de forma síncrona, pero eso no es tan importante.
  • clock_gettime y clock_nanosleep parecen ser la respuesta correcta cuando se usa con CLOCK_MONOTONIC . clock_nanosleep admite tiempos de espera absolutos, por lo que puede simplemente dormir, aumentar el tiempo de espera y repetir. También es fácil reiniciar después de una interrupción de esa manera. Desafortunadamente, estas funciones también pueden ser específicas de Linux: no hay soporte para ellas en Mac OS X o FreeBSD.
  • pthread_cond_timedwait está disponible en la Mac y puede funcionar con gettime of day como una solución alternativa, pero en la Mac solo puede funcionar con el reloj en tiempo real, por lo que está sujeto a un mal comportamiento cuando se configura el reloj del sistema o si ocurre un segundo salto.

¿Hay una API que me falta? ¿Existe una forma razonablemente portátil de crear temporizadores de intervalo de buen comportamiento en sistemas similares a UNIX, o esto resume el estado actual de las cosas?

Por buen comportamiento y razonablemente portátil, quiero decir:

  • No es propenso al sesgo del reloj (menos, por supuesto, el sesgo del reloj del sistema)
  • Resiliente al ajuste del reloj del sistema o un segundo salto
  • Capaz de soportar múltiples temporizadores en el mismo proceso
  • Disponible en al menos Linux, Mac OS X y FreeBSD

Una nota en segundos de salto (en respuesta a la respuesta de R .. ):

Los días POSIX tienen exactamente 86,400 segundos de duración, pero los días del mundo real rara vez pueden ser más largos o más cortos. La forma en que el sistema resuelve esta discrepancia está definida por la implementación, pero es común que el segundo de un salto comparta la misma marca de tiempo de UNIX que el segundo anterior. Ver también: Leap Seconds y qué hacer con ellos .

El error del segundo salto del kernel de Linux fue el resultado de una falla en la limpieza después de retrasar un segundo el reloj: https://lkml.org/lkml/2012/7/1/203 . Incluso sin ese error, el reloj habría saltado hacia atrás un segundo.


Puede consultar la pregunta here para la emulación clock_gettime , para la cual también proporcioné una respuesta, pero también me ayudó. Recientemente he agregado un temporizador simple a un pequeño repositorio que guardo para Mac OS X, que emula parcialmente las llamadas POSIX. Una simple prueba ejecuta el temporizador a 2000Hz. El repo se llama PosixMachTiming . Pruébalo.

PosixMachTiming está basado en Mach . Parece que parte de la API de Mach relacionada con el tiempo ha desaparecido de las páginas de Apple y ha quedado en desuso, pero todavía hay bits de código fuente flotando alrededor. Parece que las unidades AbsoluteTime y las abstracciones del núcleo que se encuentran aquí son la nueva forma de hacer las cosas. De todos modos, el repositorio de PosixMachTiming todavía funciona para mí.

Descripción general de PosixMachTiming

clock_gettime se emula para CLOCK_REALTIME mediante una función de llamada de la CLOCK_REALTIME que CLOCK_REALTIME el reloj en tiempo real del sistema, denominado CALENDAR_CLOCK .

El clock_gettime se emula para CLOCK_MONOTONIC utilizando una variable global ( extern mach_port_t clock_port ). Este reloj se inicializa cuando la computadora se enciende o tal vez se despierta. No estoy seguro. En cualquier caso, es la variable global a la que llama la función mach_absolute_time() .

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...) se emula mediante el uso de nanosleep en la diferencia entre la hora actual y la hora monotónica absoluta.

itimer_start() e itimer_step() se basan en llamar a clock_nanosleep durante un tiempo monotónico absoluto objetivo. Incrementa el tiempo objetivo en el intervalo de tiempo en cada iteración (no en la hora actual) para que el sesgo del reloj no sea un problema.

Tenga en cuenta que esto no satisface su requisito de poder admitir varios temporizadores en el mismo proceso.


kqueue y kevent pueden ser utilizados para este propósito. OSX 10.6 y FreeBSD 8.1 agregan soporte para EVFILT_USER , que podemos usar para activar el bucle de eventos desde otro hilo.

Tenga en cuenta que si usa esto para implementar su propia condición y espera, no necesita bloqueos para evitar condiciones de carrera, al contrario de esta excelente respuesta , porque no puede "perder" un evento en la cola.

Fuentes:

Código de ejemplo

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

Salida

$ 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