it-swarm-eu.dev

Wie kann man Thread unter Windows weniger als eine Millisekunde in den Ruhezustand versetzen?

Unter Windows habe ich ein Problem, das mir unter Unix noch nie begegnet ist. So kann ein Thread für weniger als eine Millisekunde in den Schlaf versetzt werden. Unter Unix haben Sie normalerweise eine Reihe von Optionen (Schlaf, Schlaf und Nanoschlaf), um Ihre Bedürfnisse zu erfüllen. Unter Windows gibt es jedoch nur Sleep mit Millisekunden-Granularität. 

Unter Unix kann ich mit dem Systemaufruf select einen Mikrosekunden-Ruhezustand erstellen, der ziemlich unkompliziert ist:

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

Wie kann ich das unter Windows erreichen?

50
Jorge Ferreira

Dies deutet auf ein falsches Verständnis der Schlaffunktionen hin. Der Parameter, den Sie übergeben, ist eine Minimum Zeit zum Schlafen. Es gibt keine Garantie, dass der Thread genau nach der angegebenen Zeit aufwacht. Tatsächlich „wachen“ Threads überhaupt nicht auf, sondern werden vom Scheduler zur Ausführung ausgewählt. Der Scheduler wählt möglicherweise viel länger als die angeforderte Ruhezeit, um einen Thread zu aktivieren, insbesondere wenn zu diesem Zeitpunkt noch ein anderer Thread aktiv ist.

88
Joel Coehoorn

Wie Joel sagt, können Sie für kurze Zeiträume nicht sinnvoll "schlafen" (d. H. Ihre geplante CPU aufgeben). Wenn Sie eine kurze Zeitspanne verzögern möchten, müssen Sie erneut einen entsprechend hochauflösenden Zeitgeber (z. B. den "Leistungszeitgeber") überprüfen und hoffen, dass etwas mit hoher Priorität Sie sowieso nicht vorwegnimmt.

Wenn Sie wirklich auf genaue Verzögerungen dieser kurzen Zeit achten, sollten Sie Windows nicht verwenden.

47
Will Dean

Verwenden Sie die in winmm.lib verfügbaren hochauflösenden Timer. Siehe dies für ein Beispiel.

26
Joe Schneider

Ja, Sie müssen die Zeitquanten Ihres Betriebssystems verstehen. Unter Windows erhalten Sie nicht einmal eine Auflösung von 1 ms, es sei denn, Sie ändern das Zeitquantum in 1 ms. (Zum Beispiel mit timeBeginPeriod ()/timeEndPeriod ()). Das garantiert noch nichts. Sogar eine kleine Last oder ein einzelner beschissener Gerätetreiber wird alles abwerfen.

SetThreadPriority () hilft, ist aber ziemlich gefährlich. Schlechte Gerätetreiber können Sie trotzdem ruinieren.

Sie benötigen eine extrem kontrollierte Computerumgebung, damit dieses hässliche Zeug überhaupt funktioniert.

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

ja, es verwendet einige undokumentierte Kernelfunktionen, aber es funktioniert sehr gut, ich benutze SleepShort (0.5); in einigen meiner Threads

8
Oskar Dahlberg

Wenn Sie so viel Granularität wünschen, sind Sie an der falschen Stelle (im Benutzerbereich). 

Denken Sie daran, dass Ihre Zeit nicht immer genau ist, wenn Sie sich im Benutzerraum befinden. 

Der Scheduler kann Ihren Thread (oder Ihre App) starten und planen, sodass Sie vom OS-Scheduler abhängig sind. 

Wenn Sie nach etwas genauem suchen, müssen Sie gehen: 1) Im Kernel-Bereich (wie Treiber) 2) Wählen Sie ein Echtzeitbetriebssystem.

Wenn Sie nach Granularität suchen (sich jedoch das Problem mit dem Benutzerbereich merken), schauen Sie unter MS-QueryPerformanceCounter-Funktion und QueryPerformanceFrequency-Funktion nach.

6
user16523

Wie mehrere Personen darauf hingewiesen haben, sind der Schlaf und andere verwandte Funktionen standardmäßig vom "System-Tick" abhängig. Dies ist die minimale Zeiteinheit zwischen OS-Tasks. Der Scheduler wird beispielsweise nicht schneller als dieser ausgeführt. Selbst bei einem Echtzeitbetriebssystem ist der System-Tick normalerweise nicht kleiner als 1 ms. Dies ist zwar anpassbar, hat jedoch Auswirkungen auf das gesamte System und nicht nur auf die Schlaffunktion, da der Scheduler häufiger ausgeführt wird und möglicherweise der Overhead Ihres Betriebssystems erhöht wird (Zeit für die Ausführung des Schedulers vs. Zeit, zu der eine Aufgabe ausgeführt werden kann).

Die Lösung hierfür ist die Verwendung eines externen Hochgeschwindigkeits-Taktgeräts. Bei den meisten Unix-Systemen können Sie Ihre Timer angeben und eine andere Uhr als die Standard-Systemuhr verwenden.

5
mbyrne215

Im Allgemeinen dauert ein Schlaf mindestens bis zur nächsten Systemunterbrechung. Dies hängt jedoch von den Einstellungen der Multimedia-Timer-Ressourcen ab. Es kann auf ungefähr 1 ms eingestellt werden, einige Hardware erlaubt sogar die Ausführung mit Unterbrechungsperioden von 0,9765625 (ActualResolution, bereitgestellt von NtQueryTimerResolution, zeigt 0,9766 an, aber das ist falsch Zahl in das ActualResolution -Format (0,9765625 ms bei 1024 Interrupts pro Sekunde).

Es gibt eine Ausnahme, die es uns erlaubt, der Tatsache zu entgehen, dass es unmöglich ist, für weniger als die Unterbrechungsphase zu schlafen: Es ist die berühmte Sleep(0). Dies ist ein sehr leistungsfähiges Tool, das nicht so oft verwendet wird, wie es sollte! Sie verzichtet auf die Erinnerung an die Zeitscheibe des Threads. Auf diese Weise wird der Thread angehalten, bis der Scheduler den Thread dazu zwingt, den CPU-Dienst erneut zu erhalten. Sleep(0) ist ein asynchroner Dienst. Der Aufruf zwingt den Scheduler, unabhängig von einem Interrupt zu reagieren.

Eine zweite Möglichkeit ist die Verwendung eines waitable object. Eine Wartefunktion wie WaitForSingleObject() kann auf ein Ereignis warten. Damit ein Thread jederzeit, auch im Mikrosekundenbereich, inaktiv sein kann, muss der Thread einen Servicethread einrichten, der ein Ereignis mit der gewünschten Verzögerung generiert. Der "schlafende" Thread richtet diesen Thread ein und hält dann bei der Wait-Funktion an, bis der Service-Thread das Ereignis signalisiert.

Auf diese Weise kann jeder Thread "schlafen" oder auf eine beliebige Zeit warten. Der Servicethread kann sehr komplex sein und systemweite Dienste wie zeitgesteuerte Ereignisse mit einer Auflösung von Mikrosekunden bieten. Die Auflösung im Mikrosekundenbereich kann jedoch dazu führen, dass der Servicethread höchstens einen Interrupt-Zeitraum (~ 1ms) für einen hochauflösenden Zeitdienst aktiviert. Wenn Sie vorsichtig sind, kann dies sehr gut laufen, insbesondere auf Multi-Prozessor- oder Multi-Core-Systemen. Ein 1-ms-Spin schmerzt auf einem Multicore-System nicht erheblich, wenn die Affinitätsmaske für den aufrufenden Thread und der Servicethread sorgfältig behandelt werden.

Code, Beschreibung und Tests können unter Windows Timestamp Project aufgerufen werden.

4
Arno

Worauf warten Sie noch? Wenn Sie need angeben, um diesen Genauigkeitsgrad festzulegen (z. B. aufgrund einer Abhängigkeit von externer Hardware), befinden Sie sich auf der falschen Plattform und sollten ein Echtzeitbetriebssystem verwenden.

Andernfalls sollten Sie überlegen, ob es ein Ereignis gibt, für das Sie eine Synchronisierung durchführen können, oder im schlimmsten Fall warten Sie einfach mit der CPU und verwenden die Hochleistungszähler-API, um die abgelaufene Zeit zu messen.

4
Rob Walker

Die Verwendung dieser Usleep-Funktion führt zu einem großen Speicher-/Ressourcenleck. (abhängig von wie oft genannt)

verwenden Sie diese korrigierte Version (kann leider nicht bearbeitet werden?)

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

Ich habe das gleiche problem und nichts scheint schneller zu sein als eine ms, auch der sleep (0). Mein Problem ist die Kommunikation zwischen einem Client und einer Serveranwendung, bei der ich die Funktion _InterlockedExchange zum Testen und Einstellen eines Bits und dann für den Ruhezustand (0) verwende.

Ich muss wirklich Tausende von Operationen pro Sekunde auf diese Weise durchführen und es funktioniert nicht so schnell wie ich es geplant habe.

Da ich einen Thin Client habe, der mit dem Benutzer zu tun hat, der wiederum einen Agenten aufruft, der dann mit einem Thread spricht, werde ich in Kürze den Thread mit dem Agenten zusammenführen, so dass keine Ereignisschnittstelle erforderlich ist.

Um Ihnen eine Vorstellung davon zu vermitteln, wie langsam dieser Schlaf ist, habe ich 10 Sekunden lang einen Test mit einer leeren Schleife (ungefähr 18.000.000 Schleifen) durchgeführt, während ich bei diesem Ereignis nur 180.000 Schleifen bekam. Das heißt, 100 Mal langsamer!

2
Celso Bressan

Wie von allen schon erwähnt, gibt es in der Tat keine Garantien für die Schlafzeit ... Aber niemand will zugeben, dass bei einem Leerlaufsystem der usleep-Befehl manchmal sehr genau sein kann. Besonders bei einem ticklosen Kernel. Windows Vista hat es und Linux hat es seit 2.6.16.

Tickless-Kernel existieren, um das Leben der Laptops zu verbessern. C.f. Intels Powertop-Dienstprogramm.

In diesem Zustand hatte ich den Befehl, den usleep-Befehl von Linux zu messen, der die angeforderte Ruhezeit sehr genau einschätzte, bis auf ein halbes Dutzend Mikrosekunden.

Vielleicht will das OP etwas, das auf einem System im Leerlauf meistens grob funktioniert und in der Lage ist, eine Mikro-Sekunden-Terminplanung anzufordern! Ich würde das eigentlich auch unter Windows wollen.

Auch Sleep (0) klingt wie boost :: thread :: yield (), deren Terminologie klarer ist.

Ich frage mich, ob Boost - zeitgesteuerte Schlösser eine bessere Genauigkeit haben. Dann könnten Sie einfach einen Mutex sperren, den niemand jemals freigibt, und wenn das Zeitlimit erreicht ist, fahren Sie mit ....... fort. Das Zeitlimit wird mit boost :: system_time + boost :: milliseconds & cie festgelegt (xtime ist veraltet).

1
Lightness1024

Versuchen Sie es mit SetWaitableTimer ...

0
andrewrk

Probieren Sie boost :: xtime und ein timed_wait () aus.

hat eine Genauigkeit im Nanosekundenbereich.

0
theschmitzer

Verwenden Sie einfach Sleep (0). 0 ist deutlich weniger als eine Millisekunde. Nun, das hört sich komisch an, aber ich meine es ernst. Sleep (0) teilt Windows mit, dass Sie im Moment nichts zu tun haben, aber Sie möchten erneut geprüft werden, sobald der Scheduler erneut ausgeführt wird. Da der Thread offensichtlich nicht vor der Ausführung des Schedulers ausgeführt werden kann, ist dies die kürzeste mögliche Verzögerung.

Beachten Sie, dass Sie eine Mikrosekunde-Nummer an Ihren Benutzer weitergeben können. Dies gilt jedoch auch für die Aufhebung des Zeitraums von USLEEP (__ int64 t) {Sleep (t/1000); } - Keine Garantien, um diese Zeit tatsächlich zu schlafen.

0
MSalters

Wenn Sie "eine sehr kurze Zeit warten" wollen, weil Sie spinwait ausführen, besteht eine zunehmende Wartezeit, die Sie ausführen können.

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

Wenn es also Ihr Ziel ist, nur ein wenig zu warten, können Sie Folgendes verwenden:

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

Die Tugend hier ist, dass wir jedes Mal länger warten und schließlich vollständig einschlafen. 

0
Ian Boyd

Sleep-Funktion, die weit weniger als eine Millisekunde ist - vielleicht

Ich fand, dass Schlaf (0) für mich gearbeitet hat. Auf einem System mit einer Belastung der CPU im Task-Manager von nahezu 0% schrieb ich ein einfaches Konsolenprogramm, und die sleep (0) -Funktion schlief für gleichbleibende 1-3 Mikrosekunden, was weit unter einer Millisekunde liegt.

Aber aus den obigen Antworten in diesem Thread weiß ich, dass die Anzahl der Schlafzustände (0) bei Systemen mit einer großen CPU-Auslastung viel stärker variieren kann.

Aber so wie ich es verstehe, sollte die Schlaffunktion nicht als Timer verwendet werden. Es sollte verwendet werden, damit das Programm den geringsten Prozentsatz der CPU verwendet und so oft wie möglich ausführt. Wenn ich beispielsweise ein Projektil in einem Videospiel viel schneller als einen Pixel pro Millisekunde über die Projektionsfläche schiebe, funktioniert Sleep (0), denke ich.

Sie würden nur sicherstellen, dass das Schlafintervall viel kleiner ist als die größte Zeit, die es schlafen würde. Sie verwenden den Ruhezustand nicht als Timer, sondern nur, um das Spiel mit einem möglichst geringen CPU-Prozentsatz auszustatten. Sie würden eine separate Funktion verwenden, die nichts zu tun hat, ist der Schlaf, um herauszufinden, wann eine bestimmte Zeitspanne vergangen ist, und dann das Projektil um einen Pixel auf dem Bildschirm zu bewegen - zu einer Zeit von etwa 1/10 einer Millisekunde oder 100 Mikrosekunden .

Der Pseudo-Code würde in etwa so aussehen.

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

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

//Rest of code in iteration here

Ich weiß, dass die Antwort möglicherweise nicht für fortgeschrittene Probleme oder Programme geeignet ist, aber für einige oder viele Programme. 

0
rauprog