Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

lunedì 14 settembre 2020

Sleep? No, grazie!
considerazioni sul perché non usare la sleep in C (e C++) - pt.1

soldato Hudson: Vengono fuori dalle pareti! Vengono fuori dalle fottute pareti!

Se ricordate, nell'articolo Sleeper - considerazioni su quale sleep usare in C avevo intrapreso la "missione" di demistificazione della sleep (e se non lo ricordate, la soluzione è semplice: andate a rileggervelo, male non vi farà). In quell'articolo avevo isolato le tre classiche domande che si fanno sull'uso delle funzioni di sleep: quale usare, come usare, quando usare.

E, per cominciare avevo cercato di rispondere alla prima, la più semplice. Avevo, poi, promesso di scrivere un articolo sulle altre due domande, che avrebbe dovuto intitolarsi, quindi, "considerazioni su come e quando usare la sleep"... ma ora, scrivendolo, mi sono reso conto che è meglio esporre qualcosa di un po' più radicale, qualcosa tipo "considerazioni sul perché NON USARE la sleep", e questo perché, come vedremo, gli usi sono (anzi, dovrebbero essere) molto limitati, anche se nella pratica (ahimè) non lo sono: ebbene si, come diceva il soldato Hudson nel bellissimo film Aliens, gli alieni (e, nel nostro caso, le sleep) "escono dalle fottute pareti!". Si usano e abusano decisamente troppo, ma non preoccupatevi, una volta letto quanto segue tutto sarà (spero) più chiaro.

...le sleep vengono fuori dalle fottute pareti!...
Ho diviso l'articolo in due parti, e anche questa volta cominceremo con la parte più semplice, descrivendo i pochi casi in cui ha senso utilizzare le istruzioni di sleep. Ovviamente, da qui in avanti, parleremo solo di programmazione multithread, perché è qui che l'argomento si fa critico, mentre nel singlethread una sleep serve solo a introdurre dei ritardi, e non credo che si possa aggiungere molto altro.

Cominciamo. Il caso più classico è quello di una applicazione modulare in cui abbiamo un flusso main() che avvia dei thread ("i moduli") che "fanno cose" in loop infinito, ossia eseguono delle operazioni e, a fine ciclo, le rieseguono. Insomma, una roba tipo quella che vi avevo mostrato nell'articolo sul watchdog (di nuovo: ricordate?), che, tra l'altro, si basava proprio sull'idea di sorvegliare dei thread di questo tipo per segnalare eventuali interruzioni impreviste, starvation, ecc. E già che ci sono vi ripropongo una parte della descrizione che scrissi:

Come funziona un Watchdog di terzo livello? Il modus operandi è 
abbastanza semplice, e include il rispetto di poche direttive di 
base:

- i thread da monitorare devono avere una struttura "classica", 
  e cioè quella di una funzione "che fa cose" in loop infinito con
  un opportuno intervallo di sleep tra un ciclo e l'altro.
- i thread da monitorare devono registrarsi al Watchdog prima di 
  avviare il loop infinito.
- nessuna delle cose che il thread fa nel loop deve essere bloccante: 
  ad esempio se si legge da un socket questo deve essere stato aperto 
  in modo nonblocking.
- ad ogni giro del loop (appena prima della sleep) si deve aggiornare 
  una variabile di monitoring che verrà letta dal Watchdog vero e 
  proprio.

Ecco, per oggi abbiamo finito, questo è l'unico caso in cui ha senso usare una istruzione di sleep...

E vabbè, dai, si può aggiungere ancora qualcosa. Cominciamo con un piccolo esempio che può essere usato come programma di test: è una versione semplificata di quello che avevo proposto per il watchdog, e ci permetterà di fare alcune (spero) interessanti considerazioni, anche per ampliare un po' il discorso su quale sleep usare. Vai col codice!

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>

// creo un nuovo tipo per passare dei dati ai thread
typedef struct _tdata {
    int  index;     // thread index
    bool *stop;     // flag per stop thread
} tdata;

// prototipi locali
void *faiCose(void *arg);
void Sleep(unsigned int milliseconds);

// main() - funzione main
int main(int argc, char* argv[])
{
    // init thread
    pthread_t tid[2];
    tdata     data[2];
    bool      stop = false;
    for (int i = 0; i < 2; i++) {
        // set data del thread e crea il thread
        data[i].index = i;
        data[i].stop  = &stop;
        int error;
        if ((error = pthread_create(&tid[i], NULL, &faiCose, (void *)&data[i])) != 0)
            printf("%s: non posso creare il thread %d (%s)\n", argv[0], i, strerror(error));
    }

    // dopo 10 secondi fermo i thread
    time_t start_time = time(NULL);
    for (;;) {
        // test timeout
        if (time(NULL) - start_time >= 10) {
            stop = true;
            break;
        }

        Sleep(100);
    }

    // join threads
    pthread_join(tid[1], NULL);
    pthread_join(tid[0], NULL);

    // exit
    printf("%s: thread terminati\n", argv[0]);
    return EXIT_SUCCESS;
}

// faiCose() - thread routine
void *faiCose(void *arg)
{
    // ottengo i dati del thread con un cast (tdata *) di (void *) arg
    tdata *data = (tdata *)arg;

    // thread loop
    printf("thread %d partito\n", data->index);
    unsigned long i = 0;
    for (;;) {
        // incrementa counter
        i++;

        // test stop flag
        if (*data->stop) {
            // il thread esce
            printf("thread %d terminato dal main (i = %ld))\n", data->index, i);
            pthread_exit(NULL);
        }

        // thread sleep
        Sleep(1);
    }

    // il thread esce per altro motivo che lo stop flag
    printf("thread %d terminato localmente (i = %ld)\n", data->index, i);
    pthread_exit(NULL);
}

// Sleep() - una sleep in ms
void Sleep(unsigned int milliseconds)
{
    // testa il tempo di sleep per intercettare il valore 0
    if (milliseconds > 0) {
        // usa nanosleep() o usleep()
        #if (_POSIX_C_SOURCE >= 199309L)
        struct timespec ts;
        ts.tv_sec  = milliseconds / 1000;
        ts.tv_nsec = ( milliseconds % 1000) * 1000000;
        nanosleep(&ts, NULL);
        #else
        usleep(milliseconds * 1000);
        #endif
    }
    else {
        // usa sched_yield() come alternativa Linux alla Sleep(0) di Windows
        sched_yield();
    }
}

Allora, come avrete notato stiamo usando la famosa Sleep() multi-piattaforma che avevo proposto nell'articolo "Sleeper", e grazie a questo uso possiamo anche facilmente provare la famigerata Sleep(0) e l'effetto reale che si ottinene cambiando il valore del tempo passato come argomento: vediamo una semplice tabellina con i casi che ho analizzato su una macchina Linux con un i7 (4 core e 8 thread). Per i casi inferiori al millisecondo ho modificato il codice per usare la obsoleta (ma in questo caso comoda) usleep(), e ho registrato i valori (approssimati) di uso della CPU e del counter "i" che ci mostra quanti cicli ha effettuato ogni thread:

senza sleep         i = 5314563827  CPU = 100%
Sleep(0)            i = 18863299    CPU = 100% (esegue sched_yield())
Sleep(10)   t=10ms  i = 900         CPU = 1/2%
Sleep(1)    t=1ms   i = 9000        CPU = 1/2%
usleep(100) t=100us i = 62970       CPU = 1/2%
usleep(10)  t=10us  i = 145000      CPU = 1/2%
usleep(1)   t=1us   i = 170000      CPU = 1/2%
usleep(0)   t=0us   i = 170000      CPU = 1/2% (test con usleep(0))

Il primo caso ci mostra perchè qui conviene usare la sleep: i nostri due thread funzionano bene (i due contatori avanzano simultaneamente), e la attività è gestita dal thread-scheduler (facile in questo caso: un thread dell'applicazione su un thread della CPU), ma comunque, se non lavorano volontariamente in maniera cooperativa (o gentile, se preferite) e non rilasciano ogni tanto la CPU, finiscono col mangiarsela tutta (due degli otto CPU-thread, nel mio caso), e in un sistema multiprocess non è una buona cosa, no?.

Esaminiamo, ora, il secondo caso che ci dimostra che la famosa Sleep(0)/sched_yield() di cui abbiamo parlato nell'altro articolo non da proprio dei gran risultati: i thread lavorano meno (guardare il counter) ma la CPU va lo stesso al 100%.

Il terzo e il quarto caso ci mostrano che, usando valori dell'ordine di grandezza del time-slice del sistema, la CPU lavora poco e il comportamento è lineare (fino a 1 ms).

I casi successivi mantengono il rispetto della CPU ma il comportamento diventa irregolare (i counter non aumentano proporzionalmente alla riduzione della sleep). Conclusione: in questa tipo di architettura Software (che è un buon riferimento, essendo abbastanza comune) è conveniente usare dei tempi paragonabili al time-slice del sistema, ossia tra 1 e 10 ms.

L'ultimo caso l'ho aggiunto solo per curiosità, per confermare quanto detto nell'altro articolo: usleep(0) non è equivalente a Sleep(0) (...magari qualcuno non ci credeva...), e applica il tempo di sleep minimo possibile, che è di 1 us.

E con questo abbiamo finito veramente, perché la sleep serve veramente a poco: sincronizzare i thread di una applicazione mandandone a dormire qualcuno non è proprio la maniera di operare più ottimizzata. I thread dovrebbero lavorare/fermarsi/riattivarsi in base a eventi, mentre affidarsi a intervalli di tempo (spesso scelti in maniera arbitraria) non è proprio una ideona... specialmente considerando che, una sleep con un tempo x garantisce che il thread stia fermo come minimo per quel tempo x, ma non garantisce il tempo massimo, che potrebbe essere anche molto più grande, in base a quanto è affollata la coda dei runnable-thread in attesa di partire.

E, come dicono oltreoceano, quando trovate una applicazione multithread piena di sleep ("che escono dalle fottute pareti!") siamo in presenza di un caso di "poor design", che necessita assolutamente di essere migliorato. Ma tutto questo lo vedremo nella seconda parte dell'articolo, per oggi può bastare. Mettetevi in sleep che al momento opportuno vi risveglierò io...

Ciao, e al prossimo post!

domenica 16 agosto 2020

Prototipi? Si, grazie!
considerazioni sull'uso dei prototipi in C (e C++)

(...una premessa: questo post è un remake di un mio vecchio post. Ma, anche se tratta lo stesso argomento, amplia notevolmente il discorso è affronta anche altri temi. Leggete e mi direte...

Dante: Dunque un modo per aprirla è quello della dinamite. Sistema che usava il famoso fu Cimin.
Tiberio: Fu Chi Min? Chi è, un cinese?
Dante: Ma che cinese! Veneziano era! “Fu” sarebbe che morì, Cimin è il cognome, no?!

Questo surreale dialogo tra Dante Cruciani (Totò) e Tiberio Braschi (Marcello Mastroianni) è tratto dal bellissimo I soliti ignoti, che è considerato (a ragione) uno dei prototipi della grande Commedia all'italiana. Ma cos'è un prototipo? Uhm... "Primo esemplare, modello originale di una serie di realizzazioni successive..." [Cit. Treccani]. Ecco, oggi parleremo dei Prototipi di Funzione nel linguaggio C (con qualche incursione nel C++) che sono molto somiglianti alla definizione del dizionario.

...facce da: "cosa sono sti' prototipi?"...
Dopo una rapida ispezione in rete ho notato una certa confusione sull'argomento. Prototipi obbligatori, forse consigliati, a volte sconosciuti... ho notato informazioni fuorvianti perfino in dispense universitarie (ahi, ahi). Tra l'altro, nei miei trascorsi, ho incontrato anche colleghi programmatori che non avevano le idee chiare sull'argomento. Beh, allora è giunta l'ora di fare chiarezza!

Partiamo dai dati di fatto, lasciando alla seconda parte del post le considerazioni tecniche/filosofiche sull'argomento. Mi raccomando di prestare attenzione, nel seguito del testo, ad alcune parole chiave che useremo e cercheremo di illustrare: prototipo, dichiarazione e definizione. E, faremo riferimento anche alle varie versioni del C che ci hanno accompagnato fino ad oggi che, in ordine di tempo, sono: K&R C, ANSI C (C89/C90) e C99/C11. Se non altrimenti specificato tutte le prossime affermazioni/considerazioni si riferiranno alle versioni più recenti, C99 e C11 (che non hanno, tra di loro, differenze significative su questo argomento).

Veniamo al dunque: nel C i prototipi non sono obbligatori. La confusione su questo fatto deriva dalla doppia personalità che hanno molti programmatori C (incluso il sottoscritto) che devono, spesso, districarsi tra C e C++ facendo, a volte, un po' di confusione: i prototipi sono obbligatori nel C++, per motivi strettamente collegati ad alcune funzionalità del linguaggio (vi suona il Function Overloading?).

Nel C, invece, è obbligatoria la dichiarazione di una funzione.

Facciamo, allora, un esempio sulle parole chiave dichiarazione, prototipo e definizione, usando solo una sintassi di tipo moderno (ANSI C o C99/C11):

// dichiarazione di funzione (valida ma sconsigliata perché ambigua e obsoleta)
int myFunc();

// dichiarazione di funzione con prototipo
int myFunc(int val):

// definizione di funzione con prototipo
int myFunc(int val)
{
    int retval;

    // faccio cose
    ...

    return retval;
}
L'ordine nell'esempio descritto, come evidente, non è casuale: la dichiarazione è il caso elementare, mentre il prototipo contiene implicitamente una dichiarazione e, infine, la definizione contiene implicitamente un prototipo (e quindi anche una dichiarazione). E, dato che ci siamo, aggiungiamo, per completezza, le sintassi di definizione permesse ma troppo old-fashioned, e le sintassi vietate da C99/C11:
// definizione con dichiarazione "old style" senza prototipo
int myFunc(val)
    int val;
{
    // faccio cose
    ...

    return 0;
}

// definizione implicitamente dichiarata:
// equivale a "int myFunc(int val)" (non permessa nel C99)
myFunc(val)
{
    // faccio cose
    ...

    return 0;
}
Prima di passare alla parte filosofica, facciamo una breve analisi storica: nel K&R C non c'era l'obbligo di dichiarazione delle funzioni, quindi non c'era nessun controllo a compile-time sul valore di ritorno e, ancor meno, sulla coerenza dei parametri passati: in mancanza della dichiarazione il compilatore applicava un comportamento di default e assumeva che la funzione ritornava un int. Per i parametri si applicava la default argument promotion: gli interi venivano promossi a int, e i float erano promossi a double.

Con l'avvento del ANSI C (o C89/C90), sono arrivati i prototipi, però è stata mantenuta la retro-compatibilità con la vecchia sintassi (per non obbligare a sistemare milioni di linee di codice funzionante). Con questa novità era, finalmente, possibile controllare a compile-time la correttezza d'uso delle funzioni, sia sui parametri che sui valori di ritorno. A causa della retro-compatibilità rimaneva, però, possibile scrivere nuovo codice con la sintassi antica, ed, inoltre, rimaneva valido il concetto del default return value in assenza di dichiarazione.

Con il C99 si è fatto un ulteriore passo in avanti: va bene la ricerca della compatibilità con il codice pre-esistente, ma il valore di ritorno di default era una falla troppo grande nella solidità del linguaggio, per cui si è introdotta la dichiarazione obbligatoria, come indicato all'inizio del post (aggiungo che si è anche reso obbligatorio l'uso dei prototipi negli standard headers del linguaggio, ma questa è un altra storia...).

E ora, dopo avere descritto quello che lo standard ci obbliga e/o permette di fare, veniamo, finalmente, a ciò che è meglio fare: secondo me un buon programmatore usa i prototipi (quindi, presumo, per la proprietà transitiva chi non usa i prototipi non è un buon programmatore. Ho detto presumo, quindi se qualcuno si è offeso non se la prenda con me, se la prenda con la proprietà transitiva). E perché consiglio così caldamente l'uso dei prototipi? Beh, il C è un linguaggio tipizzato, per cui è così evidente l'aiuto che questo meccanismo ci può dare per produrre codice senza errori di tipo, migliorando al tempo stesso leggibilità e manutenibilità, che non c'è neanche bisogno di spiegarlo!

E, per aggiungere un tocco di radicalità che non guasta mai, aggiungo che, per le suddette questioni di leggibilità e manutenibilità del software, non è conveniente affidarsi al fatto che, usando definizioni con prototipo (vedi esempio sopra), e scrivendo il codice nel giusto ordine (cioè usando una funzione solo dopo la sua definizione), non è necessario scrivere dei veri e propri prototipi. Non siate pigri nelle cose utili, per favore!

E come deve essere strutturato un buon codice rispetto a quanto detto sopra? Vediamo un esempio di struttura elementare con tre file:

  1. un header-file che contiene i prototipi globali.
  2. un library-file che include l'header del punto 1 e contiene le definizioni (con prototipo) delle funzioni prototipate nel header-file.
  3. un implementation-file che include l'header del punto 1, e usa le funzioni prototipate nel header-file. L'implementation-file contiene, ovviamente, anche i prototipi delle eventuali funzioni locali e le relative definizioni (con prototipo).

E con questo sarebbe tutto, anche se possiamo aggiungere una interessante curiosità un po' OT, ma che merita un approfondimento per evitare equivoci. Vediamo di cosa stiamo parlando:

// due dichiarazioni in C
int myFunc1();      // funzione con numero arbitrario di argomenti
int myFunc2(void);  // funzione con 0 argomenti

// due dichiarazioni in C++
int myFunc1();      // funzione con 0 argomenti
int myFunc2(void);  // funzione con 0 argomenti
Avete letto i commenti? Attenzione, quindi! In C int myFunc1() e int myFunc2(void) sono funzioni diverse, mentre in C++ significano la stessa cosa. Quindi occhio a non fare (in C) stranezze tipo queste:
// definizione con dichiarazione "old style" senza prototipo
int myFunc1()
{
    // faccio cose
    ...

    return 0;
}

// definizione di funzione con prototipo
int myFunc2(void)
{
    // faccio cose
    ...

    return 0;
}

// funzione main
main()
{
    ...
    myFunc1(1, 2);  // NOK: "undefined behaviour" e nessun warning/errore in compilazione
    myFunc2(1, 2);  // OK:  errore in compilazione per uso improprio della funzione
    ...
}
Quindi, usando impropriamente una funzione con dichiarazione old-style (come la myFunc1() qui sopra) si produce un undefined behaviour (come ci conferma lo standard del C99) che si potrebbe trasformare in qualche mal di testa...

Ecco, quando passate frequentemente da C a C++ (e viceversa) ricordatevi di queste cose (e di alcune altre, ma questa è un altra storia...). Comunque, una cosa è sicura: le dichiarazioni old-style sono state mantenute per retro-compatibilità, ma non usatele mai, per favore!

E ho detto tutto!

Ciao, e al prossimo post!

domenica 19 luglio 2020

Sleeper
considerazioni su quale sleep usare in C

(...una premessa: questo post potrebbe sembrare un remake di un mio vecchio post. In realtà tratta, incidentalmente, lo stesso argomento, ma amplia notevolmente il discorso è affronta ben altri temi. Leggete e mi direte...)
Luna: Il sesso è diverso oggi... vedi, noi non abbiamo nessun problema: tutti sono frigidi.
Miles: Oh ma è incredibile: gli uomini sono impotenti?
Luna: Oh sì, la maggior parte... eccetto, sì, quelli che sono di discendenza italiana.
Miles: Doveva essere qualcosa negli spaghetti!
Questo post è stato a lungo dormiente, esattamente come il Miles (Woody Allen) di Sleeper, che ha dormito 200 anni e si è risvegliato, come dire? Un po' spaesato in un epoca che non era la sua. Un post dormiente (come l'argomento) che ho deciso di svegliare perché ho accumulato letture di articoli, di quesiti (su stackoverflow) e di codice vario, che dimostrano l'esistenza di molteplici dubbi sull'uso delle funzioni di sleep: quale usare, come usare, quando usare. Ecco, in questo articolo cercherò di dare qualche spunto sulla prima (e più semplice) domanda (quale usare?). E sto già scrivendo un "considerazioni su come e quando usare la sleep", che mi costerà un po' ultimare perché è un argomento abbastanza complesso... dovrete pazientare un po'.
 
Sleeper
...te l'avevo detto di non svegliarmi...

Vediamo ora le più importanti opzioni a disposizione: sleep(), usleep(), nanosleep() sui sistemi POSIX e Sleep() su Windows. Vediamole un po' più in dettaglio, perché ognuna ha i suoi pro e contro:
  • sleep(): è Ok ma ha una risoluzione (in secondi) troppo bassa: un secondo per alcuni usi è una eternità. Ha alcune criticità rispetto ai segnali.
  • usleep(): è obsoleta: è stata bannata dagli standard POSIX perché ha una risposta ai segnali indefinita.
  • nanosleep(): è Ok: è ad alta risoluzione (in nanosecondi) e usa correttamente i segnali, ma ha una interfaccia un poco ostica...
  • Sleep(): è Ok e ha una buona risoluzione (in millisecondi) ma è solo per Windows e crea possibili problemi di porting, come vedremo più avanti.
Analizziamo per punti, saltando però la funzione sleep(): noi vogliamo analizzare l'uso della sleep come sistema di sincronizzazione nella programmazione multithread (anche se, come vedremo nel prossimo articolo, spesso non è un uso raccomandabile), quindi necessitiamo alta risoluzione (millisecondi o meno, invece dei secondi della sleep()). Vai col punto 1!

1. Lo scontro del secolo: usleep() vs nanosleep()

La funzione usleep() è stata deprecata nello standard POSIX dal 2001 ed è stata rimossa nel 2008. Per questo motivo, è consigliabile utilizzare la funzione nanosleep(), che non è obsoleta e presenta numerosi vantaggi, tra cui il comportamento relativo ai segnali:
dalla usleep() Linux Man Page:
"...4.3BSD, POSIX.1-2001.  POSIX.1-2001 declares this function obsolete; use 
nanosleep(2) instead. POSIX.1-2008 removes the specification of usleep()..."
dalla nanosleep() Linux Man Page:
"...it provides a higher resolution for specifying the sleep interval; POSIX.1 
explicitly specifies that it does not interact with signals; and it makes the 
task of resuming a sleep that has been interrupted by a signal handler easier..."
Però, come detto sopra, la nanosleep() ha un interfaccia un poco ostica, visto che "parla in nanosecondi" e oltretutto usa una struttura di appoggio per settare i tempi. Visto che a noi interessa semplificare e usare una risoluzione in millisecondi, possiamo utilizzare un semplice wrapper come questo che ho scritto (notare che ho chiamato Sleep() il wrapper, per motivi che chiarirò più avanti):
void Sleep(unsigned int milliseconds)
{
    // usa nanosleep() o usleep()
    #if (_POSIX_C_SOURCE >= 199309L)
    struct timespec ts;
    ts.tv_sec  = milliseconds / 1000;
    ts.tv_nsec = (milliseconds % 1000) * 1000000;
    nanosleep(&ts, NULL);
    #else
    usleep(milliseconds * 1000);
    #endif
}
Faccio notare che, nelle versioni più recenti della  glibc (e anche nella ottima musl) il wrapper è quasi già integrato: dato che usleep(), pur essendo deprecata, è ancora usata in molti programmi, per portabilità viene implementata (più o meno) così:
int usleep(useconds_t useconds)
{
    struct timespec ts = {
        .tv_sec = useconds/1000000,
        .tv_nsec = (useconds%1000000)*1000
    };

    return nanosleep(&ts, NULL);
}
E, non ci crederete, ma nelle versioni più recenti di glibc anche la sleep() è implementata usando la nanosleep(), che si dimostra il vero e proprio prezzemolino delle funzioni di questo tipo!

E adesso che abbiamo a disposizione un bel wrapper è il momento dei parlare di (possibili) problemi di portabilità. Vai col punto 2!

2. Il mistero della Sleep(0)

Ho usato per il wrapper qui sopra una sintassi simile alla Sleep() di Windows, proprio per evidenziare un possibile uso nella scrittura di Software "portabile" (o meglio: multi-piattaforma). Quindi, per questo nuovo uso, possiamo migliorare il wrapper nella maniera seguente:
#ifndef WIN32   // ridefinisce la Sleep() solo in ambiente POSIX
void Sleep(unsigned int milliseconds)
{
    // usa nanosleep() o usleep()
    #if (_POSIX_C_SOURCE >= 199309L)
    struct timespec ts;
    ts.tv_sec  = milliseconds / 1000;
    ts.tv_nsec = (milliseconds % 1000) * 1000000;
    nanosleep(&ts, NULL);
    #else
    usleep(milliseconds * 1000);
    #endif
}
#endif
In questa maniera se cerchiamo di usare lo stesso codice su sistemi POSIX o su Windows useremo automaticamente la funzione opportuna (ossia: il wrapper o quella di sistema) senza modificare il codice. Ma ci sono alcune problematiche da tenere in conto: la sleep di Windows non è interrompibile dai segnali (che su Windows non ci sono) e, soprattutto, è possibile usare Sleep(0). E a cosa serve una istruzione di questo tipo? Vediamo cosa dice il manuale:
dalla Sleep() MSDN Doc Page:
"...A value of zero causes the thread to relinquish the remainder of its time 
slice to any other thread that is ready to run. If there are no other threads 
ready to run, the function returns immediately, and the thread continues execution..."
Ossia, Sleep(0) non tiene a dormire il thread chiamante come minimo per i millisecondi impostati (che è il comportamento classico), ma invece, praticamente, permette a eventuali thread in attesa di avviarsi e, se non ce ne sono in attesa, il thread chiamante riprende il funzionamento senza nessun ritardo. Faccio notare che, in realtà, Windows ha una funzione ad-hoc per questo, la SwitchToThread(), ma è abbastanza usuale usare Sleep(0). E faccio anche notare che Sleep(0) è una istruzione da usare con molta parsimonia, perché ha molti effetti collaterali se non è usata nei punti giusti del codice (aumento esagerato dei thread context switch, aumento dell'uso di CPU, etc.).

Comunque, tornando al nostro wrapper, voi mi direte: "e quale è il problema? Il wrapper chiamerà usleep o nanosleep con un tempo zero, quindi problema risolto!". E invece no, perché, sfortunatamente, alcune implementazioni UNIX/Linux delle varie funzioni di sleep non hanno, con un "time to sleep zero", un comportamento equivalente a quello della Sleep(0). Anzi, in alcuni casi (come la sleep(), ad esempio) il codice inizia con qualcosa del genere:
if (time_to_sleep == 0)
    return;
Quindi la nostra Sleep(0) sui sistemi POSIX non è completamente compatibile con quella dei sistemi Windows... Che si fa allora? Ma è semplice, ridefiniamo ancora il nostro wrapper come segue:
#ifndef WIN32   // ridefinisce la Sleep() solo in ambiente POSIX
void Sleep(unsigned int milliseconds)
{
    // testa il tempo di sleep per intercettare il valore 0    
    if (milliseconds > 0) {
        // usa nanosleep() o usleep()
        #if (_POSIX_C_SOURCE >= 199309L)
        struct timespec ts;
        ts.tv_sec  = milliseconds / 1000;
        ts.tv_nsec = ( milliseconds % 1000) * 1000000;
        nanosleep(&ts, NULL);
        #else
        usleep(milliseconds * 1000);
        #endif
    }
    else {
        // usa sched_yield() come alternativa Linux alla Sleep(0) di Windows 
        sched_yield();
    }
}
#endif
La funzione sched_yield() è l'equivalente POSIX di Sleep(0) e SwitchToThread() su Windows: e anche questa bisogna usarla con attenzione perché può avere effetti collaterali (sul carico della CPU, etc.) ma, poiché sono gli stessi effetti collaterali della versione Windows, si può ragionevolmente supporre che sia possibile effettuare una sostituzione diretta (ottenendo, spesso, gli stessi risultati disastrosi).
dalla sched_yield() Linux Man Page:
"...sched_yield() causes the calling thread to relinquish the CPU. The thread is
moved to the end of the queue for its static priority and a new thread gets to run..."
E per oggi può bastare. Nell'articolo "considerazioni su come e quando usare la sleep" (che non sarà necessariamente il prossimo post) entreremo molto più in profondità sull'argomento. E faremo un confronto con gli altri metodi di sincronizzazione dei thread e si capirà che, a parte alcuni casi, non è una buona idea scrivere codice che usa molto le funzioni di sleep. Pazienza e non trattenete il respiro nell'attesa!

Ciao, e al prossimo post!