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

StackOverflow https://stackoverflow.com/questions/85122

  •  01-07-2019
  •  | 
  •  

Question

Sous 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 un certain nombre de choix (sommeil, sommeil et nanosans) pour répondre à vos besoins. Sous Windows, cependant, il n’existe que Veille avec une granularité à la milliseconde.

Sous Unix, je peux utiliser l'appel système select pour créer une veille 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 le même résultat sous Windows?

Était-ce utile?

La solution 18

Sous Windows, l'utilisation de select vous oblige à inclure le Winsock bibliothèque qui doit être initialisée ainsi dans votre application:

WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);

Ensuite, la sélection ne vous autorisera pas à être appelée sans socket, vous devez donc en faire un peu plus pour créer une méthode de microsleep:

int usleep(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/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, &dummy, &tv);
}

Toutes ces méthodes usleep créées renvoient zéro en cas de succès et non nulle pour les erreurs.

Autres conseils

Ceci indique une mauvaise compréhension des fonctions de sommeil. Le paramètre que vous transmettez est un délai minimum 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 fil, en particulier si un autre fil est toujours actif à ce moment-là.

Comme le dit Joel, vous ne pouvez pas "dormir" de manière significative (c’est-à-dire abandonner votre CPU programmé) 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éemptera pas de toute façon.

Si vous tenez vraiment aux retards précis de délais aussi courts, vous ne devriez pas utiliser Windows.

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

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 merdique jettera tout.

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

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

#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

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 avec l’espace utilisateur), cherchez Fonction QueryPerformanceCounter et fonction QueryPerformanceFrequency dans MSDN.

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 ordonnanceur s’exécutera plus fréquemment et augmentera potentiellement la charge de travail de votre système d’exploitation (durée de son exécution, la durée d'exécution d'une tâche).

La solution à ce problème 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.

Qu'attendez-vous pour que cela demande une telle précision? En général, si vous avez besoin de 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 de synchroniser un événement ou, dans le pire des cas, d'attendre le processeur et d'utiliser l'API du compteur de performances élevées pour mesurer le temps écoulé.

En règle générale, une veille durera au moins jusqu'à la prochaine interruption du système. Cependant, cela dépend des réglages des ressources du minuteur multimédia. Il peut être réglé sur quelque chose proche de 1 ms, certains matériels permettent même de s'exécuter à des périodes d'interruption de 0.9765625 ( ActualResolution fournies par NtQueryTimerResolution afficheront 0.9766, mais ce n'est en réalité pas correct. Ils ne peuvent tout simplement pas indiquer le nombre correct dans le 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 pendant moins que la période d'interruption: c'est le fameux Sleep (0) . C'est un très puissant outil et il n’est pas utilisé aussi souvent qu’il le devrait! Il abandonne le rappel de la tranche de temps du fil. De cette façon, le thread s'arrêtera jusqu'à ce que le planificateur le force à récupérer le service cpu. Sleep (0) est un service asynchrone, l'appel forcera le planificateur à réagir indépendamment d'une interruption.

L’utilisation d’un objet en attente est un autre moyen. 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 " dormir " Le thread configurera ce thread, puis fera une pause dans la fonction wait jusqu'à ce que le thread de service définisse l'événement signalé.

Ainsi, n'importe quel fil peut "dormir". ou attendre n'importe quand. Le fil de service peut être très complexe 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 des précautions sont prises, cela peut fonctionne très bien, en particulier sur les systèmes multi-processeurs ou multi-core. 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 soigneusement gérés.

Le code, la description et les tests peuvent être visités à l'adresse du projet d'horodatage Windows

.

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 sur laquelle 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, lequel appelle à son tour 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!

En fait, l’utilisation de cette fonction usleep provoquera 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;
}

Comme chacun l’a mentionné, il n’ya aucune garantie quant à la durée du 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 et Linux depuis le 2.6.16.

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

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

Alors, peut-être que le PO veut quelque chose qui fonctionnera à peu près la plupart du temps sur un système en veille, et pourra demander une programmation en micro-seconde! En fait, je voudrais aussi cela sur Windows.

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

Je me demande si les boost ont une meilleure qualité précision. Vous pouvez alors verrouiller un mutex que personne ne publie jamais et, une fois le délai d'attente écoulé, continuez ... Les délais d'attente sont définis avec boost :: heure_système + boost :: millisecondes & amp; cie (xtime est obsolète).

Essayez d’utiliser SetWaitableTimer ...

Essayez boost :: xtime et timed_wait ()

a une précision nanoseconde.

Utilisez simplement 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 le thread ne peut évidemment pas être programmé avant que le planificateur lui-même ne soit exécuté, 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.

La fonction de veille est bien inférieure à la milliseconde - peut-être

J'ai constaté 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 bien inférieur à une milliseconde.

Mais, à partir des réponses ci-dessus dans ce fil, je sais que la durée du sommeil en sommeil (0) peut varier beaucoup plus énormément que celle-ci 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.

Assurez-vous simplement que l'intervalle de sommeil est bien plus court que la durée de sommeil la plus longue. 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 à peu près la même chose.

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 les problèmes ou les programmes avancés, mais peut-être pour certains ou pour plusieurs programmes.

Si votre objectif est "d'attendre très peu de temps" parce que vous effectuez un tournage en attente , vous pouvez effectuer une attente de plus en plus longue. .

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 un peu , vous pouvez utiliser quelque chose du type:

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top