7
votes

Interaction d'application multi-threadée avec filetage de l'enregistreur

Ici, je suis à nouveau avec des questions sur le multi-threading et un exercice de ma catégorie Chant de programmation .

J'ai un serveur multi-threadé - implémenté en utilisant le modèle de programmation .NET asynchrone - avec obtenez ( télécharger ) et mettre ( téléchargez ). Cette partie est effectuée et testée.

Il arrive que le relevé du problème indique que ce serveur doit avoir l'activité avec l'impact minimum sur le temps de réponse du serveur, et il devrait être Pris en charge par une priorité basse thread - thread de l'enregistreur - créé pour cet effet. Tous les les messages doivent être passés par les threads qui les produisent à cet le fil d'enregistreur , à l'aide d'un mécanisme de communication que ne peut pas verrouillez le fil qui l'invoque (en plus du verrouillage nécessaire pour assurer l'exclusion mutuelle) et en supposant que certains les messages de journalisation peuvent être ignorés.

ici Est-ce que ma solution actuelle, s'il vous plaît aider à la validation si cela constitue une solution au problème indiqué: xxx

essentiellement, voici les points principaux de ce code: < ol>

  • Démarrez une priorité basse thread qui boucle la file d'attente de journalisation et imprime en attente journaux à la sortie. Après cela, le fil est suspendu jusqu'au nouveau journal arrive;
  • Lorsqu'un journal arrive, le fil de l'enregistreur est réveillé et fait-il fonctionner.

    est cette solution thread-coffre ? J'ai lu les producteurs-consommateurs algorithme de problèmes et de solution, mais dans ce problème, bien que j'ai plusieurs producteurs, je n'ai qu'un seul lecteur.


  • 0 commentaires

    4 Réponses :


    1
    votes

    En fait, vous introduisez le verrouillage ici. Vous avez le verrouillage lorsque vous appuyez sur une entrée de journal dans la file d'attente (méthode du journal): Si 10 threads ont poussé simultanément 10 éléments dans la file d'attente et réveillés le fil de l'enregistreur, le 11ème thread sera attendu jusqu'à ce que le fil de l'enregistreur enregistre tous les articles ...

    Si vous voulez quelque chose de vraiment évolutif - implémenter la file d'attente sans verrouillage (l'exemple est ci-dessous). Avec le mécanisme de synchronisation de la file d'attente sans verrouillage sera vraiment tout de suite (vous pouvez même utiliser une seule poignée d'attente unique pour les notifications).

    Si vous ne parvenez pas à trouver la mise en œuvre de la file d'attente sans verrouillage sur le Web, voici une idée de comment faire ceci: Utilisez la liste liée pour une implémentation. Chaque nœud de la liste liée contient une référence et une référence volatile au nœud suivant. Par conséquent, pour les opérations Enqueue et Dequeue, vous pouvez utiliser la méthode Interlocked.comPareExchange. J'espère que l'idée est claire. Si non - laissez-moi savoir et je vais fournir plus de détails.


    3 commentaires

    Salut. Merci beaucoup pour votre réponse, mais j'aimerais vraiment éviter une solution qui utilise des mécanismes verrouillés. Il est dit dans la déclaration d'exercice que je peux utiliser le verrouillage, bien que des fins d'exclusion mutuelle. Je ne peux pas envisager le mécanisme "de verrouillage" de ma solution pour correspondre à cet objectif, car je suis simplement en train d'essayer d'assurer l'exclusion mutuelle accédant à la file d'attente de journalisation?


    @XPIRITOO: Vous ne pouvez effectuer aucun travail sans verrouillage multiprocesseur sans verrouillage sans verrouillage sans les instructions verrouillées (ni leurs équivalents de GCC). Si vous utilisez un verrou de mutex, vous n'avez pas besoin de verrouillés. Mais la suggestion de Vitaliy était de construire une file d'attente sans verrouillage.


    OK, si vous n'avez pas besoin d'exigences de performance critiques - votre code au premier aspect a l'air de thread-coffre-fort.



    4
    votes

    Il semble que cela devrait fonctionner. Les producteurs-consommateurs ne devraient pas changer grandement en cas de consommateur unique. Petits nitpicks:

    • Acquisition de verrouillage peut être une opération coûteuse (comme @Vitaliy Lipchinsky dit). Je recommanderais de comparer votre enregistreur contre l'enregistreur et l'enregistreur naïfs "écriture" à l'aide d'opérations imbriquées. Une autre alternative n'échangerait que la file d'attente existante avec un vide vide dans getlog et quittant la section critique immédiatement. Cette façon aucune des producteurs ne sera bloquée par de longues opérations dans les consommateurs.

    • Mme LogOBJ Type de référence (classe). Il ne sert à rien de faire la structure car vous la boxez de toute façon. Ou bien faire _Queue de type logOBJ [] (c'est mieux quand même).

    • Faites votre fond de fil de manière à éviter de fermer votre programme si stop ne sera pas appelé.

    • Flush votre TextWriter . Ou sinon vous risquez de perdre même ces enregistrements qui ont géré la file d'attente (10 articles sont un peu petit IMHO)

    • Implément Idisposable et / ou finisseur. Votre enregistreur possède un fil de fil et de texte et ceux-ci devraient être libérés (et rinçage - voir ci-dessus).


    1 commentaires

    Merci pour vos considérations. Va certainement changer mon code en conséquence.



    3
    votes

    Bien que cela semble être le fil-coffre-fort, je ne crois pas qu'il est particulièrement optimal. Je suggérerais une solution sur ces lignes

    note: juste lire les autres réponses. Ce qui suit est une solution de verrouillage optimiste assez optimale basée sur la vôtre. Les principales différences sont en train de verrouiller sur une classe interne, minimisant ainsi les «sections critiques» et fournissant une résiliation gracieuse du fil. Si vous souhaitez éviter de verrouiller tout à fait, vous pouvez essayer une partie de cette liste liée «non verrouillante» volatile que @Vitaliy lipchinsky suggère. xxx


    4 commentaires

    Salut. Merci de m'avoir souvenu de moi d'utiliser une terminaison de fil gracieuse, au lieu d'une méthode de "Abandon" puissante. Je vais absolument adopter ça!


    Au début, je n'ai pas remarqué des problèmes que j'ai trouvés dans votre code, lorsque j'ai essayé d'adapter ma solution à la suite de vos suggestions. En fait, vous ne pouvez pas "pulser" ou "attendre" sur un objet de synchronisation en dehors de la portée "verrouille" correspondante. Considérant cela, il n'y a pas de gain sur la création d'une copie locale de la file d'attente de l'enregistreur sur votre méthode "getLog"; Il n'est pas non plus possible de réduire l'acquisition de verrouillage sur la file d'attente, dans votre méthode de «journal». En outre, certaines méthodes que vous faites référence n'existent pas. La file d'attente n'a pas de méthode "Tolist ()" (seulement un "toarray ()") et "moniteur.pulse ()" doit recevoir un paramètre (objet de synchronisation) ...


    Ahem, leçon importante, jamais "aile" une réponse. L'utilisation d'un moniteur [correctement] est synchronisée avec l'utilisation de la serrure. Puisque nous utilisons le verrouillage pour protéger notre file d'attente, le moniteur est totalement superflu. Supprimez-le [à partir de deux solutions ci-dessus et \ ou votre solution précédente]. Ce n'est pas nécessaire. Il y a beaucoup de gain de créer une copie locale de la file d'attente. N'effectuez jamais de traitement dans une section critique. Vous souhaitez accéder à votre ressource partagée et publiez-la. Traitement [c'est-à-dire l'écriture à la console, le fichier, le RPC, tout ce que] peut être effectué ultérieurement. Tolist est une méthode d'extension disponible dans .NET3.5, à l'aide de System.Linq


    Eu, par "aile" une réponse, faisait référence à moi évidemment. aurait dû rechercher le moniteur plus tôt, aurait repéré la redondance. : S



    0
    votes

    Je fais juste une expérience de pensée ici, car je n'ai pas le temps d'essayer le code en ce moment, mais je pense que vous pouvez le faire sans serrures du tout si vous êtes créatif.

    Demandez à votre classe de journalisation contenant une méthode qui alloue une file d'attente et un sémaphore à chaque fois qu'elle s'appelle (et une autre qui transmet la file d'attente et le sémaphore lorsque le fil est effectué). Les threads qui souhaitent faire la journalisation appelleront cette méthode lorsqu'ils commencent. Quand ils veulent se connecter, ils poussent le message à leur propre file d'attente et définissent le sémaphore. Le fil de l'enregistreur a une grosse boucle qui traverse les files d'attente et vérifie les sémaphores associés. Si le sémaphore associé à la file d'attente est supérieur à zéro, la file d'attente est apparue et le sémaphore décrémenté.

    Parce que vous n'essayez pas de faire sortir les choses de la file d'attente avant que le sémaphore soit défini, et que vous ne définissez pas le sémaphore qu'après avoir poussé les choses sur la file d'attente, je pense Ce sera en sécurité. Selon la documentation MSDN pour la classe de file d'attente, si vous énumérez la file d'attente et un autre thread modifie la collection, une exception est lancée. Attraper cette exception et vous devriez être bon.


    0 commentaires