2 votes

Monitor.Pulse en C# semble sous-optimal : il doit être dans la portée du verrou.

Note spoiler : la question est la dernière phrase.

En C#, le modèle classique d'utilisation d'une variable de condition est le suivant :

lock (answersQueue)
{
    answersQueue.Enqueue(c);
    Monitor.Pulse(answersQueue); // condition variable "notify one".
}

et un autre fil :

lock (answersQueue)
{
    while (answersQueue.Count == 0)
    {
        // unlock answer queue and sleeps here until notified.
        Monitor.Wait(answersQueue);
    }
    ...
}

c'est un exemple tiré de mon code. si je place le Pulse en dehors de la portée du verrou, il ne compile pas. cependant, c'est la manière correcte : c.f :

http://msdn.microsoft.com/en-us/library/Windows/desktop/ms686903(v=vs.85).aspx et : http://www.installsetupconfig.com/win32programming/threadprocesssynchronizationapis11_7.html (recherche de "inside")

Et en effet, il est idiot de signaler le fil dormant alors que vous êtes encore dans votre section critique. Parce que le fil endormi ne peut pas se réveiller (pas immédiatement), parce qu'il se trouve également à l'intérieur d'une section critique !

Par conséquent, j'espère que l'appel à l'impulsion de .NET ou C# ne fait que marquer l'objet de verrouillage, de sorte que lorsqu'il sort de sa portée, il "impulse" la variable de condition à ce moment-là. Parce que sinon, il y aurait un problème d'optimalité.

Alors comment se fait-il que la conception de l'objet Monitor ait été choisie pour être de cette façon ?

Editar:

J'ai trouvé la réponse dans ce document : http://research.microsoft.com/pubs/64242/implementingcvs.pdf la section "Optimisation du signal et de la diffusion" et la section précédente sur le noyau NT et la façon de rendre les conditions variables en plus des sémaphores, ce qui est la raison de l'introduction des "maudites files d'attente". Maintenant, cela fait de moi un meilleur ingénieur.

1voto

Marc Gravell Points 482669

Et en effet, il est idiot de signaler le fil endormi lorsque vous êtes encore dans votre section critique. Parce que le fil endormi ne peut pas se réveiller

Pulse ne s'attend pas pour faire tourner un thread ; il s'attend seulement à déplacer un thread entre les 2 files d'attente (waiting et ready). Le "ne pas aller faire quelque chose" fait partie de la libération du verrou par l'intermédiaire de Exit (ou la fin d'un lock ). En réalité, ce n'est pas un problème car Monitor.Pulse se produit généralement juste avant une Wait ou un Exit .

Par conséquent, j'espère que l'appel à l'impulsion de .NET ou C# ne fait que marquer l'objet de verrouillage, de sorte que lorsqu'il sort de sa portée, il "impulse" la variable de condition à ce moment-là. Parce que sinon, il y aurait un problème d'optimalité.

Encore une fois, il s'agit de problèmes différents : passer de l'état d'attente à l'état prêt est une chose ; sortir d'un verrou déjà contient tout le code nécessaire pour activer le prochain fil prêt.

1voto

quetzalcoatl Points 8814

Vous n'avez pas compris le problème de base de la synchronisation. Qu'est-ce qu'un 'moniteur', qu'est-ce que cela signifie qu'un thread dort et qu'il est sur le point d'être réveillé ?

Un moniteur est une structure de synchronisation de niveau intermédiaire. Il ne s'agit pas d'un petit drapeau booléen volatil de bas niveau avec une opération XCHG de détournement de bus, ni d'un gestionnaire de pool de threads de haut niveau qui nécessite des dizaines d'autres mécanismes spéciaux

Sur un moniteur, MANY les fils peuvent dormir. Il y a files d'attente logiques Je ne vais pas entrer dans les détails, tout ce qui existe est déjà connu, comme par exemple l'ordre d'endormissement et de réveil, ou les mécanismes qui garantissent un emploi du temps adéquat. Je n'entrerai pas dans les détails, tout cela est disponible sur le web, même sur wiki.

Ajoutez à cela que l'opération est PULSE. L'impulsion est instantanée. Elle ne "colle" pas. L'impulsion va réveiller ceux qui sont en train de dormir. Si, après l'impulsion, une autre personne vérifie le moniteur, celui-ci se met en veille.

Maintenant imaginez : vous avez une file d'attente de 5 fils dormants. Un thread (6ème) veut maintenant les pulser, et un autre (7ème) veut vérifier le moniteur.

Les 6e et 7e s'exécutent en parallèle, vraiment simultanément, puisque vous avez un processeur à quatre cœurs.

Alors, dites-moi, qu'arriverait-il à l'implémentation de la file d'attente si le 6ème commençait à pulser, réveiller et enlever les fils réveillés de la file d'attente, et dans le même temps le 7ème commençait à s'y ajouter ?

Pour résoudre ce problème, les files d'attente internes devraient être synchronisées et verrouillées en interne, afin qu'un seul thread à la fois puisse les modifier.

Hum, attendez. On vient de tomber sur un cas où on voulait SYNCHRONISER quelque chose, et pour le faire correctement, on doit SYNCHRONISER sur une autre chose ? C'est pas bon.

Par conséquent, le VERROUILLAGE réel est effectué EXTERNE avant que vous ne parliez au moniteur lui-même. Cela permet d'obtenir un VERROUILLAGE UNIQUE, au lieu d'introduire plusieurs couches de verrous hiérarchiques.

De cette façon, c'est plus simple, plus rapide et plus respectueux des ressources.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X