En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

lunes, 4 de enero de 2016

Mad Sleep
cómo escribir un wrapper para la nanosleep() en C

La sleep() tiene personalidades múltiples, tal vez está un poco loca, al igual que nuestro amigo Max. Si queremos enviar a dormir por un tiempo la nuestra aplicación (normalmente un thread de una aplicación, pero vamos a tratar este tema en otra ocasión...) tenemos muchas (¿demasiadas?) opciones: sleep(), usleep(), nanosleep(), ¡y más todavia!
...¿a quién has llamado loco?
Vamos a centrarnos en las tres mencionadas anteriormente: cada una tiene sus pros y sus contras:
  • sleep(): está bien, pero tiene una resolución demasiado baja: un segundo es una eternidad para algunas aplicaciones.
  • usleep(): es obsoleta: fue eliminada por el estándar Posix, ya que tiene una respuesta a las señales indefinida.
  • nanosleep(): es OK (alta resolución, trata las señales), pero tiene una interfaz un poco complicada...
Para el uso normal (se podría decir  usos no hard-realtime, pero vamos a tratar este tema en otra ocasión...) nos necesitaría una función con el interfaz de la sleep() y una resolución media, yo diría en milisegundos. Pues, entonces, ¡hacemos un wrapper para la usleep()!

¡Vamos con el código!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// prototipos locales
static void mySleep(long msec);
static char *getDateUsec(char *date);

// main del programa de test
int main(int argc, char **argv)
{
    // test argumentos
    if (argc != 1) {
        // error
        printf("Usage: %s\n", argv[0]);
        return EXIT_FAILURE;
    }

    // enseña la hora, sleep y enseña otra vez la hora
    char date[32];
    printf("hora antes de la sleep:  %s\n", getDateUsec(date));
    mySleep(1500);
    printf("hora después de la sleep:%s\n", getDateUsec(date));

    // esce con Ok
    return EXIT_SUCCESS;
}

// mySleep - sleep para un numero especifico de milisegundos
void mySleep(
    long msec)              // sleep time en milisegundos
{
    // split argumento en segundos y milisegundos (msec está en milisegundos pero podría ser > 1000)
    unsigned int seconds      = msec / 1000;
    unsigned int milliseconds = msec % 1000;

    // set datos para nanosleeep() y los usa
    struct timespec t;
    t.tv_sec  = seconds;
    t.tv_nsec = milliseconds * 1000000; // i.e.: tv_nsec=500000000; 500 millones de ns es 1/2 sec
    nanosleep(&t, NULL);
}

// getDateUsec - obtiene date-time con los microsegundos
char *getDateUsec(
    char* date)             // string destinación
{
    ...
}
Y, como siempre:
  1. Codigo que se auto-explica, ampliamente comentado y... los comentarios hablan por sí mismos.
  2. La función main() no hace más que arrancar la mySleep() y a enseñar (con los datos de tiempo antes y después) que la función trabaja.
    La mySleep() es el nuestro wrapper, y se puede integrar fácilmente en cualquier aplicación o en una librería. Internamente utiliza la nanosleep(), formateando oportunamente los datos a pasar con el único argumento (en milisegundos) que le ofrecemos. El main() muestra (con una precisión de microsegundos) el tiempo antes y después de la mySleep(), utilizando otra función escrita ad-hoc, la getDateUsec(): esta es una nuestra vieja conocida descrita aqui, y por eso no he reportado su código (bueno, tal vez asi vais a leer el antiguo puesto donde fue descrita).

    Una última nota para (eventuales) súper-perfeccionistas que habrán pensado, al leer el código: "pero el split de los argumentos se podría hacer directamente en las asignaciones a struct timespec t, ahorrando las dos instrucciones de asignación precedentes...". Bueno, no voy a perder tiempo discutiendo y los invito sólo en reflexionar sobre los siguientes tres puntos:
    1. mySleep() sirve para retrasar: sabéis lo que nos importa la eficiencia en este caso...
    2. Leer esto.
    3. "The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming" (D.Knuth, "Computer Programming as an Art", 1974).
    Os recuerdo una vez más que, para probar el programa en Linux (lo cual hice, por supuesto...) es suficiente compilar el programa con:
    gcc mysleep.c -o mysleep
    ...uhmmm... tengo que admitir que la mención del mítico D.Knuth es la parte más interesante de todo el post. Meditar gente, meditar...

    ¡Hasta el próximo post!