web-dev-qa-db-fra.com

Comment faire en sorte que les threads dorment moins d'une milliseconde sous Windows

Sur Windows, j'ai un problème que je n'ai jamais rencontré sous Unix. C'est comment faire en sorte qu'un fil s'endorme moins d'une milliseconde. Sous Unix, vous avez généralement plusieurs choix (sommeil, sommeil et nanosans) pour répondre à vos besoins. Sous Windows, cependant, il n'y a que Sleep avec une granularité à la milliseconde. 

Sous Unix, je peux utiliser l'appel système select pour créer un sommeil microseconde assez simple:

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

Comment puis-je obtenir la même chose sous Windows?

50
Jorge Ferreira

Cela indique une mauvaise compréhension des fonctions du sommeil. Le paramètre que vous passez est un minimum time pour dormir. Il n'y a aucune garantie que le thread se réveille exactement à l'heure spécifiée. En fait, les threads ne «se réveillent» pas du tout, mais sont plutôt choisis pour être exécutés par le planificateur. Le planificateur peut choisir d'attendre beaucoup plus longtemps que la durée de veille demandée pour activer un thread, en particulier si un autre thread est toujours actif à ce moment.

88
Joel Coehoorn

Comme Joel le dit, vous ne pouvez pas "dormir" de manière significative (c’est-à-dire abandonner votre CPU planifié) pendant de si courtes périodes. Si vous souhaitez différer un court instant, vous devez effectuer une rotation, en vérifiant à plusieurs reprises un minuteur de haute résolution (par exemple, le «minuteur de performances») et en espérant que quelque chose de haute priorité ne vous préempte de toute façon pas.

Si vous vous souciez vraiment des retards précis de temps aussi courts, vous ne devriez pas utiliser Windows.

47
Will Dean

Utilisez les minuteries haute résolution disponibles dans winmm.lib. Voir this pour un exemple.

26
Joe Schneider

Oui, vous devez comprendre les quantités de temps de votre système d'exploitation. Sous Windows, vous n'obtiendrez même pas des temps de résolution de 1 ms à moins de changer le temps quantique à 1ms. (En utilisant par exemple timeBeginPeriod ()/timeEndPeriod ()) Cela ne garantit toujours pas vraiment quoi que ce soit. Même une petite charge ou un seul pilote de périphérique de merde va tout jeter.

SetThreadPriority () aide, mais est assez dangereux. De mauvais pilotes de périphériques peuvent encore vous ruiner.

Vous avez besoin d'un environnement informatique ultra-contrôlé pour faire fonctionner ce truc laid.

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

oui, il utilise des fonctions non documentées du noyau, mais cela fonctionne très bien, j'utilise SleepShort (0.5) dans certains de mes thred

8
Oskar Dahlberg

Si vous voulez une telle granularité, vous êtes au mauvais endroit (dans l'espace utilisateur). 

N'oubliez pas que si vous êtes dans l'espace utilisateur, votre temps n'est pas toujours précis. 

Le planificateur peut démarrer votre thread (ou application) et le planifier afin que vous dépendiez du planificateur de système d'exploitation. 

Si vous recherchez quelque chose de précis, vous devez vous rendre: 1) Dans l’espace noyau (comme les pilotes) 2) Choisissez un RTOS.

Quoi qu'il en soit, si vous recherchez une certaine granularité (mais souvenez-vous du problème d'espace utilisateur), cherchez Fonction QueryPerformanceCounter et fonction QueryPerformanceFrequency dans MSDN.

6
user16523

Comme plusieurs personnes l'ont souligné, les fonctions sommeil et autres fonctions connexes dépendent par défaut du "tick système". Il s'agit de l'unité de temps minimale entre les tâches du système d'exploitation. le planificateur, par exemple, ne fonctionnera pas plus vite que cela. Même avec un système d'exploitation en temps réel, le tic-tac du système n'est généralement pas inférieur à 1 ms. Même s’il est réglable, cela a des conséquences pour l’ensemble du système, pas seulement pour votre fonctionnalité de veille, car votre planificateur fonctionnera plus fréquemment et risque d’augmenter la charge de votre système fois qu'une tâche peut être exécutée).

La solution consiste à utiliser un dispositif d'horloge externe à haute vitesse. La plupart des systèmes Unix vous permettront de spécifier vos horloges et une horloge différente à utiliser, par opposition à l'horloge système par défaut.

5
mbyrne215

Généralement, un sommeil durera au moins jusqu'à la prochaine interruption du système. Toutefois, cela dépend des réglages des ressources de la minuterie multimédia. Il peut être proche de 1 ms, certains matériels permettent même de fonctionner à des périodes d’interruption de 0.9765625 (ActualResolution fourni par NtQueryTimerResolution indiquera 0.9766 mais c’est faux nombre au format ActualResolution. Il est 0,9765625 ms à 1024 interruptions par seconde).

Il y a une exception qui nous permet d'échapper au fait qu'il peut être impossible de dormir moins que la période d'interruption: c'est la fameuse Sleep(0). C'est un outil très puissant Et il n'est pas utilisé aussi souvent qu'il le devrait! Il abandonne le rappel de la tranche de temps du fil. Ainsi, le thread s'arrêtera jusqu'à ce que le planificateur l'oblige à obtenir à nouveau le service cpu. Sleep(0) est un service asynchrone, l'appel forcera le planificateur à réagir indépendamment d'une interruption.

Une deuxième méthode consiste à utiliser un waitable object. Une fonction d'attente telle que WaitForSingleObject() peut attendre un événement. Afin de laisser un thread en veille à tout moment, y compris à des instants de la microseconde, le thread doit configurer un thread de service qui générera un événement au délai souhaité. Le thread "en sommeil" va configurer ce thread, puis faire une pause dans la fonction wait jusqu'à ce que le thread de service définisse l'événement signalé.

De cette façon, tout fil peut "dormir" ou attendre à tout moment. Le fil de service peut être d'une grande complexité et offrir des services à l'échelle du système, tels que des événements programmés à une résolution de la microseconde. Toutefois, une résolution en microsecondes peut forcer le thread de service à fonctionner sur un service à temps de résolution élevé pendant au plus une période d'interruption (~ 1 ms). Si vous prenez des précautions, cela peut très bien fonctionner, en particulier sur les systèmes multiprocesseurs ou multicœurs. Une rotation d'une minute ne nuit pas beaucoup au système multicœur, lorsque le masque d'affinité pour le thread appelant et le thread de service sont gérés avec précaution.

Le code, la description et les tests peuvent être consultés à l’adresse Projet d’horodatage Windows

4
Arno

Qu'attendez-vous pour que cela demande une telle précision? En général, si vous avez besoin pour spécifier ce niveau de précision (par exemple, en raison d’une dépendance à un matériel externe), vous êtes sur la mauvaise plate-forme et vous devez utiliser un système d'exploitation en temps réel.

Sinon, vous devriez envisager la possibilité de synchroniser un événement ou, dans le pire des cas, attendre que le processeur soit occupé et utiliser l'API du compteur hautes performances pour mesurer le temps écoulé.

4
Rob Walker

En fait, l’utilisation de cette fonction usleep entraînera une fuite importante de mémoire/de ressources. (selon combien de fois appelé)

utiliser cette version corrigée (désolé impossible d'éditer?)

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

J'ai le même problème et rien ne semble être plus rapide qu'un ms, même le sommeil (0). Mon problème est la communication entre un client et une application serveur où j'utilise la fonction _InterlockedExchange pour tester et définir un bit puis I Sleep (0).

J'ai vraiment besoin d'effectuer des milliers d'opérations par seconde de cette façon et cela ne fonctionne pas aussi vite que prévu.

Comme j'ai un client léger qui traite avec l'utilisateur, qui à son tour appelle un agent qui parle ensuite à un thread, je vais bientôt fusionner le thread avec l'agent de sorte qu'aucune interface d'événement ne soit requise.

Juste pour vous donner une idée de la lenteur de ce sommeil, j’ai fait un test pendant 10 secondes en effectuant une boucle vide (obtenant quelque chose comme 18 000 000 boucles) alors qu’avec l’événement en place, je n’ai que 180 000 boucles. C'est-à-dire 100 fois plus lent!

2
Celso Bressan

Comme tout le monde le dit, rien ne garantit le temps de sommeil ... Mais personne ne veut admettre que, parfois, sur un système inactif, la commande usleep peut être très précise. Surtout avec un noyau sans tic. Windows Vista l’a et Linux l’a depuis 2.6.16.

Les noyaux Tickless existent pour aider à améliorer la vie des ordinateurs portables: c.f. Utilitaire powertop d'Intel.

Dans ces conditions, j’ai dû mesurer la commande Linux usleep qui respectait de très près le temps de veille demandé, jusqu’à une demi-douzaine de microsecondes.

Donc, peut-être que le PO veut quelque chose qui fonctionne à peu près la plupart du temps sur un système en veille, et qui soit capable de demander une programmation en micro-secondes! En fait, je le voudrais aussi sous Windows.

De plus, Sleep (0) sonne comme boost :: thread :: yield (), dont la terminologie est plus claire.

Je me demande si les verrous chronométrés Boost - ont une meilleure précision. Vous pouvez alors verrouiller un mutex que personne ne publie jamais et, lorsque le délai d'attente est atteint, continuer sur ... Les délais d'attente sont définis avec boost :: system_time + boost :: millisecondes & cie (xtime est obsolète).

1
Lightness1024

Essayez d’utiliser SetWaitableTimer ...

0
andrewrk

Essayez boost :: xtime et timed_wait ()

a une précision nanoseconde.

0
theschmitzer

Il suffit d'utiliser Sleep (0). 0 est clairement inférieur à une milliseconde. Ça a l'air drôle, mais je suis sérieux. Sleep (0) indique à Windows que vous n'avez rien à faire pour le moment, mais que vous souhaitez être reconsidéré dès que le planificateur s'exécutera à nouveau. Et comme il est évident que le thread ne peut pas être planifié avant que le planificateur lui-même ne s'exécute, il s'agit du délai le plus court possible.

Notez que vous pouvez transmettre un nombre en microsecondes à votre compte utilisateur, mais il en va de même pour usleep (__ int64 t) {Sleep (t/1000); } - aucune garantie de dormir réellement cette période.

0
MSalters

Si votre objectif est de "attendre très peu de temps" parce que vous effectuez un spinwait , l'attente que vous pouvez effectuer augmente.

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

Donc si votre objectif est d’attendre seulement un petit peu , vous pouvez utiliser quelque chose comme:

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

La vertu ici est que nous attendons plus longtemps à chaque fois, pour finir par nous endormir complètement. 

0
Ian Boyd

Fonction de sommeil beaucoup moins qu'une milliseconde, peut-être

J'ai trouvé que dormir (0) fonctionnait pour moi. Sur un système avec une charge proche de 0% sur le processeur dans le gestionnaire de tâches, j'ai écrit un programme de console simple et la fonction sleep (0) a dormi pendant 1 à 3 microsecondes, ce qui est nettement inférieur à la milliseconde.

Mais parmi les réponses ci-dessus dans ce fil, je sais que la quantité de sommeil (0) peut varier beaucoup plus énormément que cela sur des systèmes avec une charge de processeur importante.

Mais si je comprends bien, la fonction de veille ne devrait pas être utilisée comme une minuterie. Il doit être utilisé pour que le programme utilise le moins de pourcentage possible de la CPU et s’exécute aussi souvent que possible. Pour mes besoins, comme déplacer un projectile sur l'écran dans un jeu vidéo beaucoup plus rapidement qu'un pixel à la milliseconde, sommeil (0) fonctionne, je pense.

Vous voudriez simplement vous assurer que l'intervalle de sommeil est bien plus petit que la durée maximale de sommeil. Vous n'utilisez pas le sommeil comme une minuterie mais juste pour que le jeu utilise le minimum de pourcentage de cpu possible. Vous utiliseriez une fonction distincte qui n'a rien à faire est de dormir pour savoir quand un laps de temps particulier s'est écoulé, puis de déplacer le projectile d'un pixel sur l'écran, à un moment de 1/10ème de milliseconde ou 100 microsecondes. .

Le pseudo-code donnerait quelque chose comme ça.

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

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

//Rest of code in iteration here

Je sais que la réponse peut ne pas fonctionner pour des problèmes ou des programmes avancés, mais peut-être pour un ou plusieurs programmes. 

0
rauprog