it-swarm.it

Come rendere il thread dormire meno di un millisecondo su Windows

Su Windows ho un problema che non ho mai incontrato su Unix. Ecco come far dormire un thread per meno di un millisecondo. Su Unix hai in genere un numero di scelte (sleep, usleep e nanosleep) per soddisfare le tue esigenze. Su Windows, tuttavia, c'è solo Sleep con una granularità al millisecondo. 

Su Unix, posso usare la chiamata di sistema select per creare un sonno in microsecondi che è piuttosto semplice:

int usleep(long usec)
{
    struct timeval tv;
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, 0, &tv);
}

Come posso ottenere lo stesso su Windows?

50
Jorge Ferreira

Ciò indica una comprensione errata delle funzioni del sonno. Il parametro che passi è un minimo tempo per dormire. Non c'è alcuna garanzia che il thread si riattivi dopo esattamente il tempo specificato. In effetti, i thread non si attivano affatto, ma vengono scelti per l'esecuzione dallo scheduler. Lo scheduler potrebbe scegliere di attendere molto più tempo della durata del sonno richiesta per attivare un thread, specialmente se un altro thread è ancora attivo in quel momento.

88
Joel Coehoorn

Come dice Joel, non è possibile "dormire" in modo significativo (cioè abbandonare la CPU programmata) per periodi così brevi. Se vuoi ritardare per un po 'di tempo, allora devi girare, controllare ripetutamente un timer ad alta risoluzione (ad esempio il "timer delle prestazioni") e sperare che qualcosa di alta priorità non ti precluda comunque.

Se davvero ti preoccupi dei ritardi precisi di tempi così brevi, non dovresti usare Windows.

47
Will Dean

Utilizzare i timer ad alta risoluzione disponibili in winmm.lib. Vedi questo per un esempio.

26
Joe Schneider

Sì, è necessario comprendere i quantum del tempo del proprio sistema operativo. Su Windows, non otterrai nemmeno 1ms di risoluzione, a meno che non cambi il tempo in 1ms. (Usando per esempio timeBeginPeriod ()/timeEndPeriod ()) che comunque non garantisce nulla. Anche un piccolo carico o un singolo driver di dispositivo schifoso buttano via tutto.

SetThreadPriority () aiuta, ma è piuttosto pericoloso. I cattivi driver di dispositivo possono ancora rovinarti.

Hai bisogno di un ambiente informatico ultra-controllato per far funzionare tutte queste brutte cose.

9
darron
#include <Windows.h>

static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");

static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");




static void SleepShort(float milliseconds) {
    static bool once = true;
    if (once) {
        ULONG actualResolution;
        ZwSetTimerResolution(1, true, &actualResolution);
        once = false;
    }

    LARGE_INTEGER interval;
    interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
    NtDelayExecution(false, &interval);
}

sì usa alcune funzioni del kernel non documentate, ma funziona molto bene, io uso SleepShort (0.5); in alcuni dei miei thri

8
Oskar Dahlberg

Se vuoi così granularità sei nel posto sbagliato (nello spazio utente). 

Ricorda che se ti trovi nello spazio utente il tuo tempo non è sempre preciso. 

Lo scheduler può avviare il thread (o l'app) e pianificarlo, in modo che dipendano dallo scheduler del SO. 

Se stai cercando qualcosa di preciso devi andare: 1) Nello spazio del kernel (come i driver) 2) Scegli un RTOS.

Ad ogni modo se stai cercando qualche granularità (ma ricorda il problema con lo spazio utente) guarda a Funzione QueryPerformanceCounter e funzione QueryPerformanceFrequency in MSDN.

6
user16523

Come diverse persone hanno sottolineato, il sonno e altre funzioni correlate dipendono di default dal "tick di sistema". Questa è l'unità di tempo minima tra le attività del sistema operativo; lo scheduler, ad esempio, non verrà eseguito più velocemente di così. Anche con un sistema operativo in tempo reale, il segno di spunta di sistema non è in genere inferiore a 1 ms. Sebbene sia sintonizzabile, ciò ha implicazioni per l'intero sistema, non solo per la funzionalità di sospensione, poiché lo scheduler verrà eseguito più frequentemente e potenzialmente aumentando il sovraccarico del sistema operativo (quantità di tempo per l'esecuzione dello scheduler, rispetto all'importo di tempo in cui un compito può essere eseguito).

La soluzione a questo è utilizzare un dispositivo di clock esterno ad alta velocità. La maggior parte dei sistemi Unix ti consente di specificare i tuoi timer e un orologio così diverso da usare, al contrario dell'orologio di sistema predefinito.

5
mbyrne215

Generalmente un sonno durerà almeno fino al prossimo interrupt di sistema. Tuttavia, questo .__ dipende dalle impostazioni delle risorse del timer multimediale. Può essere impostato su qualcosa di simile a 1 ms, alcuni hardware consentono anche di eseguire a intervalli di interrupt di 0.9765625 (ActualResolution fornito da NtQueryTimerResolution mostrerà 0.9766 ma questo è in realtà sbagliato. numero nel formato ActualResolution. È 0.9765625ms a 1024 interrupt al secondo).

C'è un'eccezione che ci permette di evitare il fatto che potrebbe essere impossibile dormire per meno del periodo di interruzione: è il famoso Sleep(0). Questo è uno strumento .__ molto potente e non è usato tutte le volte che dovrebbe! Rinuncia al promemoria della sezione temporale del thread. In questo modo il thread si fermerà fino a quando lo scheduler non forza il thread per ottenere di nuovo il servizio cpu. Sleep(0) è un servizio asincrono, la chiamata costringerà lo scheduler a reagire indipendentemente da un interrupt.

Un secondo modo è l'uso di un waitable object. Una funzione di attesa come WaitForSingleObject() può attendere un evento. Per avere un thread che dorme per un po 'di tempo, anche i tempi nel regime di microsecondi, il thread deve impostare qualche thread di servizio che generi un evento al ritardo desiderato. Il thread "sleeping" imposterà questo thread e quindi metterà in pausa nella funzione wait finché il thread del servizio imposterà l'evento segnalato.

In questo modo, qualsiasi thread può "dormire" o attendere in qualsiasi momento. Il thread di servizio può essere di grande complessità e può offrire servizi a livello di sistema come gli eventi a tempo con risoluzione microseconda. Tuttavia, la risoluzione in microsecondi può forzare il servizio thread a girare su un servizio orario ad alta risoluzione per un massimo di un periodo di interruzione (~ 1 ms). Se si presta attenzione, questo può funzionare molto bene, in particolare su sistemi multiprocessore o multi-core. Una rotazione di un ms non danneggia in modo considerevole sul sistema multi-core, quando la maschera di affinità per il thread chiamante e il thread di servizio sono gestiti con cura.

Codice, descrizione e test possono essere visitati al Windows Timestamp Project

4
Arno

Cosa stai aspettando richiede una tale precisione? In generale, se si ha bisogno di per specificare quel livello di precisione (ad esempio a causa di una dipendenza da qualche hardware esterno) si è sulla piattaforma sbagliata e si dovrebbe guardare ad un sistema operativo in tempo reale.

Altrimenti dovresti considerare se c'è un evento su cui puoi eseguire la sincronizzazione oppure, nel caso peggiore, attendere la CPU e utilizzare l'API del contatore ad alte prestazioni per misurare il tempo trascorso.

4
Rob Walker

In realtà, l'uso di questa funzione addormentata causerà una grande perdita di memoria/risorse. (dipende da quanto spesso chiamato)

usa questa versione corretta (scusa non posso modificare?)

bool usleep(unsigned long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec / 1000000ul;
    tv.tv_usec = usec % 1000000ul;
    bool success = (0 == select(0, 0, 0, &dummy, &tv));
    closesocket(s);
    return success;
}
2
Hendrik

Ho lo stesso problema e nulla sembra essere più veloce di un ms, anche lo Sleep (0). Il mio problema è la comunicazione tra un client e un'applicazione server in cui utilizzo la funzione _InterlockedExchange per testare e impostare un bit e poi I Sleep (0).

Ho davvero bisogno di eseguire migliaia di operazioni al secondo in questo modo e non funziona così velocemente come avevo pianificato.

Poiché ho un thin client che gestisce l'utente, che a sua volta richiama un agente che poi parla con un thread, mi sposterò presto per unire il thread con l'agente in modo che non sia richiesta alcuna interfaccia di evento.

Solo per darti un'idea di quanto sia lento questo Sleep, ho eseguito un test per 10 secondi eseguendo un ciclo vuoto (ottenendo qualcosa come 18.000.000 di loop) mentre con l'evento in atto ho ottenuto solo 180.000 loop. Cioè, 100 volte più lento!

2
Celso Bressan

Come menzionato da tutti, non c'è davvero alcuna garanzia circa il tempo di sonno . Ma nessuno vuole ammettere che a volte, su un sistema inattivo, il comando usleep può essere molto preciso. Soprattutto con un kernel senza tick. Windows Vista lo ha e Linux lo ha dal 2.6.16.

Esistono kernel tickless per contribuire a migliorare la vita dei laptop: c.f. L'utility powertop di Intel.

In quella condizione, ho sperimentato di aver misurato il comando Linux usleep che rispettava il tempo di sonno richiesto molto da vicino, fino a una mezza dozzina di micro secondi.

Quindi, forse l'OP vuole qualcosa che funziona più o meno a lungo su un sistema inattivo, ed essere in grado di chiedere una programmazione micro-secondo! Veramente lo vorrei anche su Windows.

Anche Sleep (0) suona come boost :: thread :: yield (), la cui terminologia è più chiara.

Mi chiedo se Boost - le serrature temporizzate abbiano una precisione migliore. Perché in questo caso puoi semplicemente bloccare un mutex che nessuno rilascia mai, e quando viene raggiunto il timeout, continua su ... I timeout sono impostati con boost :: system_time + boost :: milliseconds & cie (xtime è deprecato).

1
Lightness1024

Prova a usare SetWaitableTimer ...

0
andrewrk

Prova boost :: xtime e a timed_wait ()

ha una precisione di nanosecondi.

0
theschmitzer

Basta usare Sleep (0). 0 è chiaramente inferiore a un millisecondo. Ora, sembra divertente, ma sono serio. Sleep (0) dice a Windows che non hai nulla da fare in questo momento, ma che vuoi essere riconsiderato non appena lo scheduler viene eseguito di nuovo. E poiché ovviamente non è possibile pianificare l'esecuzione del thread prima dell'esecuzione dello scheduler, questo è il ritardo più breve possibile.

Si noti che è possibile passare un numero di microsecondi al proprio addormentato, ma lo stesso vale per il fatto che usleep us (__ int64 t) {Sleep (t/1000); } - Nessuna garanzia per dormire effettivamente quel periodo.

0
MSalters

Se il tuo obiettivo è "attendere un tempo molto breve" perché stai facendo un spinwait, allora ci sono livelli crescenti di attesa che puoi eseguire.

void SpinOnce(ref Int32 spin)
{
   /*
      SpinOnce is called each time we need to wait. 
      But the action it takes depends on how many times we've been spinning:

      1..12 spins: spin 2..4096 cycles
      12..32: call SwitchToThread (allow another thread ready to go on time core to execute)
      over 32 spins: Sleep(0) (give up the remainder of our timeslice to any other thread ready to run, also allows APC and I/O callbacks)
   */
   spin += 1;

   if (spin > 32)
      Sleep(0); //give up the remainder of our timeslice
   else if (spin > 12)
      SwitchTothread(); //allow another thread on our CPU to have the remainder of our timeslice
   else
   {
      int loops = (1 << spin); //1..12 ==> 2..4096
      while (loops > 0)
         loops -= 1;
   }
}

Quindi se il tuo obiettivo è in realtà aspettare solo per un po ', puoi usare qualcosa come:

int spin = 0;
while (!TryAcquireLock()) 
{ 
   SpinOne(ref spin);
}

La virtù qui è che aspettiamo ogni volta più a lungo, finendo per addormentarci completamente. 

0
Ian Boyd

Funzione sleep che è molto meno di un millisecondo, forse

Ho trovato che dormire (0) ha funzionato per me. Su un sistema con un carico prossimo allo 0% sulla CPU in task manager, ho scritto un semplice programma di console e la funzione sleep (0) ha dormito per 1-3 microsecondi coerenti, ovvero meno di un millisecondo.

Ma dalle risposte di cui sopra in questo thread, so che la quantità di sonno sonno (0) può variare molto più selvaggiamente di questo su sistemi con un carico di CPU di grandi dimensioni.

Ma a quanto ho capito, la funzione sleep non dovrebbe essere utilizzata come timer. Dovrebbe essere usato per fare in modo che il programma utilizzi la percentuale minima della CPU possibile ed eseguito il più frequentemente possibile. Per i miei scopi, come spostare un proiettile sullo schermo in un videogame molto più veloce di un pixel al millisecondo, sleep (0) funziona, penso.

Dovresti semplicemente assicurarti che l'intervallo di sonno sia molto inferiore alla maggiore quantità di tempo in cui potrebbe dormire. Non si usa il sonno come timer, ma solo per fare in modo che il gioco usi la quantità minima di percentuale di CPU possibile. Dovresti usare una funzione separata che non ha nulla da fare è dormire per sapere quando è passata una determinata quantità di tempo e quindi spostare il proiettile di un pixel sullo schermo, ad esempio 1/10 di millisecondo o 100 microsecondi .

Lo pseudo-codice andrebbe in questo modo.

while (timer1 < 100 microseconds) {
sleep(0);
}

if (timer2 >=100 microseconds) {
move projectile one pixel
}

//Rest of code in iteration here

So che la risposta potrebbe non funzionare per problemi o programmi avanzati, ma potrebbe funzionare per alcuni o molti programmi. 

0
rauprog