Question Thread.Sleep (0): Quel est le comportement normal?


À ma connaissance, un Thread.Sleep (0) force un changement de contexte sur le système d'exploitation.

Je voulais vérifier quelle était la quantité maximale de temps pouvant passer dans une application avant de recevoir du temps CPU.

J'ai donc construit une application qui fait Thread.Sleep (0) dans une boucle while (c #) et calcule le temps qui passe entre chaque appel.

Lorsque cette application est la seule à fonctionner sur un PC de test à deux cœurs, la durée maximale observée est inférieure à 1 milliseconde (avec une moyenne de 0,9 microseconde) et elle utilise tout le processeur disponible (100%).

Lorsque je l'exécute avec une application fictive de remplissage du processeur (toutes ayant la même priorité), le temps maximum est d'environ 25 ms et le temps moyen est de 20 ms. Il se comporte exactement comme je l'attends. Et le temps est très stable.

Chaque fois que le temps CPU prend du temps, il redonne immédiatement le contrôle à ceux qui ont un traitement à effectuer, comme le jeu de patate chaude (l'utilisation du processeur tombe à 0%). Si aucune autre application n'est en cours d'exécution, le contrôle revient immédiatement.

Compte tenu de ce comportement, je m'attendais à ce que cette application ait un impact minimal sur un ordinateur exécutant une application réelle. (Et pour me donner la "latence" réelle que je pourrais espérer voir dans les applications qui y sont exécutées). Mais à ma grande surprise, cela a affecté négativement (de manière observable) la performance de ce système spécifique.

Est-ce qu'il me manque un point important concernant Thread.Sleep (0)?

En référence, voici le code de cette application

private bool _running = true;
private readonly Stopwatch _timer = new Stopwatch();

private double _maxTime;
private long _count;
private double _average;
private double _current;

public Form1()
{
    InitializeComponent();
    Thread t = new Thread(Run);
    t.Start();
}

public void Run()
{
    while(_running)
    {
        _timer.Start();
        Thread.Sleep(0);
        _timer.Stop();

        _current = _timer.Elapsed.TotalMilliseconds;
        _timer.Reset();
        _count++;

        _average = _average*((_count - 1.0)/_count) + _current*(1.0/_count);
        if(_current>_maxTime)
        {
            _maxTime = _current;
        }
    }
}

Édité pour plus de clarté (but de l'application): Je suis en train d’exécuter une application multi-thread en temps réel (bien, un groupe d’applications) qui doit réagir à certaines entrées à peu près 300 ms, mais nous manquons parfois des délais (moins de 1% du temps) et J'essaie actuellement d'améliorer ce nombre.

Je voulais vérifier quelle est la variabilité actuelle causée par d’autres processus sur la même machine: il est difficile d’adapter l’application écrite ci-dessus sur cette machine semi-temps-réel pour savoir quelle variabilité est provoquée par le système. C'EST À DIRE. J'ai 300ms mais le temps maximum observé avant qu'un thread ait du temps CPU est de 50ms, donc pour améliorer les performances, je devrais régler mon temps de traitement à 250ms maximum (car je suis peut-être déjà à 50ms de retard).


21
2017-07-15 16:29


origine


Réponses:


Il ne Obliger un changement de contexte, seul Sleep (1) le fait. Mais s'il y a un autre thread de n'importe quel processus prêt à fonctionner et qu'il a une priorité plus élevée, Sleep (0) générera le processeur et le laissera s'exécuter. Vous pouvez voir cela en exécutant une boucle sans fin qui appelle Sleep (0), il va graver 100% des cycles du processeur sur un noyau. Je ne comprends pas pourquoi vous n'observez pas ce comportement.

La meilleure façon de garder le système réactif est de donner une priorité faible à votre thread.


31
2017-07-15 16:34



J'ai été mordu par ce bug dans un projet précédent. J'avais un thread en cours d'exécution qui vérifiait les messages dans une file d'attente prioritaire, à la recherche d'un nouveau. S'il ne trouvait aucun nouveau message, je souhaitais que le thread s'endorme jusqu'à ce qu'un message ajouté à la file d'attente le réveille pour vérifier à nouveau.

Naïvement, en supposant que Thread.Sleep(0) Cela mettrait le thread en veille jusqu'à ce que je me réveille à nouveau, j'ai trouvé notre application consommant des quantités folles de CPU une fois que les messages commençaient à arriver.

Après quelques jours de recherche de causes possibles, nous avons trouvé l’information de ce lien.  La solution rapide était d'utiliser Thread.Sleep(1). Le lien contient les détails de la raison de la différence, y compris une petite application de test en bas, démontrant ce qui arrive aux performances entre les 2 options.


6
2017-07-15 18:48



La raison la plus probable est que vous ne permettez pas au programme de lire les instructions de manière efficace.

Quand vous invoquez Sleep(0) Votre code est suspendu pour un cycle unique puis programmé pour le prochain emplacement disponible. Cependant, ce changement de contexte n'est pas gratuit - il y a beaucoup de registres à enregistrer / charger, et lorsque vous avez une séquence d'instructions différente à lire à partir du disque, vous vous retrouvez probablement avec quelques erreurs de cache. Je ne peux pas imaginer que cela ait un impact significatif sur votre application dans la plupart des cas, mais si vous travaillez avec un système en temps réel ou quelque chose d'intensif similaire, cela pourrait être une possibilité.


3
2017-07-15 16:37



Je crois comprendre que Thread.Sleep (0) ne Obliger un changement de contexte de thread, il signale simplement au planificateur de tâches que vous êtes prêt à abandonner le reste de votre tranche de temps s'il y a d'autres threads en attente d'exécution.

Votre boucle autour de Sleep (0) grignote le temps processeur, ce qui aura un effet négatif sur les autres applications (et sur la durée de vie de la batterie de votre ordinateur portable!). Sleep (0) ne signifie pas "laisser tout le reste s'exécuter en premier", votre boucle sera donc en concurrence pour le temps d'exécution avec d'autres processus.

Passer un temps d’attente non nul à Sleep () serait légèrement meilleur pour d’autres applications car cela obligerait à mettre ce thread en attente pour un minimum de temps. Mais ce n’est pas encore la manière dont vous implémentez un thread d’arrière-plan à impact minimum.

La meilleure façon d'exécuter un thread d'arrière-plan lié au processeur avec un impact minimum sur les applications de premier plan consiste à réduire votre priorité de thread à un niveau inférieur à la normale. Cela indiquera au planificateur d'exécuter tous les threads de priorité normaux en premier, et si / quand il y a un autre temps disponible, exécutez votre thread de faible priorité. L'effet secondaire de ceci est que, parfois, votre thread de faible priorité peut ne pas avoir de temps d'exécution du tout pendant des périodes de temps relativement longues (en secondes) en fonction de la saturation du processeur.


3
2017-07-15 16:42



Je pense que vous avez remarqué que l'impact de votre autre programme était uniquement lié au processeur. Par conséquent, il existe également un troisième programme exécutant votre programme sans prendre en compte le planificateur du système d'exploitation.

Le programme non impacté est arrêté par le système d'exploitation pour permettre à votre programme de s'exécuter, et il existe un accès contextuel pour échanger le processus, puis charger le vôtre puis le décharger à nouveau.


0
2017-07-15 16:36