4 votes

Quel est le but de l'introduction de 'save queue' dans l'instruction erlang receive ?

Je suis nouveau dans le monde d'Erlang et je commence le tutoriel de 'Programming Erlang' de Joe Armstrong.

Je suis perplexe quant à la "file d'attente de sauvegarde" mentionnée dans la réception sélective en 8.6. Si le message ne correspond pas du tout, pourquoi ne pas le laisser tomber directement ? Quel est l'intérêt de le remettre dans la boîte aux lettres pour un traitement ultérieur ? Si c'est le comportement par défaut, ces messages poubelles (c'est-à-dire qu'ils ne peuvent pas être appariés) pourraient entraîner des pénalités de performance car ils s'accumulent sans être libérés.

Je voudrais savoir si j'ai mal compris cette partie. J'ai essayé d'inspecter le contenu d'une boîte aux lettres de processus pour mieux comprendre, mais je n'y suis pas parvenu.

Veuillez m'aider, j'apprécierais tout extrait de code qui prouve un point, merci.

10voto

Pascal Points 11550

Ce paragraphe du livre décrit les détails de ce qui est fait par un bloc de réception. N'oubliez pas qu'il est possible d'avoir plusieurs blocs de réception traités séquentiellement par un seul processus. Ainsi, un message qui ne correspond à aucune entrée du premier bloc de réception sera placé dans la file d'attente de sauvegarde (pour des raisons d'efficacité) :

  • il ne correspondra jamais à une entrée du bloc de réception actuel,
  • le bloc de réception en cours est assuré de ne se terminer que lorsqu'un message entrant correspond à une entrée ou que le délai d'attente se termine.

Lorsqu'un bloc de réception est terminé, la file d'attente de sauvegarde est remise, dans l'ordre de réception original, dans la boîte aux lettres, car le bloc de réception suivant a une chance d'avoir une entrée qui correspond à l'un des messages de la "file d'attente de sauvegarde".

L'un des usages est de gérer les priorités :

loop() ->
   ...
   receive
      {high_prio,Msg} -> process_priority_message(Msg)
   after 0
      ok  % no priority message
   end,

   receive
      {low_prio,Msg} -> process_normal_message(Msg);
      Other -> process_unexpected_message(Other)
   end,
   ...
   loop()

Ce code permet de traiter un message {high_prio,Msg} même s'il n'est pas en première position dans la file d'attente des messages.

Et vous avez raison, il y a un risque que les messages inattendus s'accumulent dans la boîte aux lettres, surtout dans une boucle sans fin, c'est pourquoi vous verrez très souvent quelque chose comme la dernière ligne

Autre -> process_unexpected_message(Autre)

pour vider la boîte aux lettres.

3voto

rvirding Points 13019

Il s'agit d'une aide précieuse lors de la mise en place de systèmes concurrents, car elle vous permet de vous concentrer uniquement sur les messages qui vous intéressent. à ce moment-là et ignore les autres messages. Les systèmes Erlang sont typiquement non-déterministes, donc vous savez rarement si vous avez besoin d'aide. ce que que vous allez recevoir et quand . Sans la file d'attente de sauvegarde automatique, cela signifierait qu'à chaque fois que vous recevez un message, vous devez être en mesure de traiter tous les messages qui pourraient arriver à ce processus. Cela devient très vite une explosion combinatoire d'états et de messages.

Prenons l'exemple d'un serveur simple. Au niveau supérieur, il y aura une boucle de réception qui recevra les demandes à traiter par le serveur. Il traitera ensuite la première demande. Pendant ce traitement, il communiquera probablement avec d'autres processus et recevra des messages. Pendant le traitement d'une demande, un nouveau message de demande peut arriver au serveur. Si Erlang ne sauvegardait pas les messages, vous devriez traiter ces requêtes partout dans le code du serveur où des messages sont reçus. Maintenant vous pouvez ignorer ces nouvelles requêtes et les laisser pour la boucle supérieure qui devrait les manipuler.

Garder la trace de tous les messages qui doivent être traités quelque part dans votre serveur devient rapidement irréalisable. Par exemple, dans un gen_server vous avez les demandes réelles envoyées par les clients (format de message non spécifié), les messages "système" au serveur (format de message non spécifié), un nombre quelconque de messages bien définis qui sont significatifs pour le code du serveur en plus de tous les messages dont votre traitement des demandes a besoin.

Vous finiriez par mettre en œuvre votre propre tampon de messages et le faire circuler.

L'absence de file d'attente pour la sauvegarde des messages rendrait pratiquement impossible l'écriture de modules génériques qui envoient/reçoivent des messages en cours de traitement, par exemple les fonctions client pour gen_server s. Ils doivent connaître tous les messages qui peuvent arriver à ce processus et qui doivent être traités.

Oui, en sauvegardant tous les messages puede être un problème, mais c'est généralement le type de problème qui peut être résolu lorsque vous en êtes conscient. Par exemple, dans la boucle supérieure d'un serveur, vous pouvez être raisonnablement certain qu'un message inconnu peut être reçu et jeté. A gen_server reçoit tous les messages à son niveau supérieur où il sait quoi faire avec eux, certains qu'il traite lui-même (les messages du système) et les autres qu'il transmet au code spécifique du serveur.

Et il permet de gérer facilement les messages prioritaires comme l'a montré @Pascal.

2voto

tow Points 559

+1 à rvirding, "C'est une aide énorme lors de la construction de systèmes concurrents" est exactement ce dont il s'agit. Cela m'a rappelé un exemple de code - une solution au "Smokers Problem" - que j'ai créé il y a quelque temps. J'ai pensé que le partager pourrait aider à illustrer le propos :

-module(smokers).
-export([smokers/0]).

smokers() ->
    Rounds = 1000000,
    Agent = self(),
    lists:foreach(fun(Material) -> spawn(fun() -> startSmoker(Agent, Material) end) end, materials()),
    done = agent(Rounds),
    io:format("Done ~p rounds~n", [Rounds]).

agent(0) ->
    done;
agent(Rounds) ->
    offer(twoRandomMaterials(), Rounds).

offer(AvailableMaterials, Rounds) ->
    receive
        {take, Smoker, AvailableMaterials} ->
            Smoker ! smoke,
            receive
                doneSmoking ->
                    agent(Rounds - 1)
            end
    end.

startSmoker(Agent, Material) ->
    smoker(Agent, lists:delete(Material, materials())).

smoker(Agent, Missing) ->
    Agent ! {take, self(), Missing},
    receive
        smoke ->
            Agent ! doneSmoking,
            smoker(Agent, Missing)
    end.

twoRandomMaterials() ->
    Materials = materials(),
    deleteAt(random:uniform(length(Materials)) - 1, Materials).

materials() ->
    [paper, tobacco, match].

deleteAt(_, []) -> [];
deleteAt(0, [_ | T]) -> T;
deleteAt(Idx, [H | T]) -> [H | deleteAt(Idx - 1, T)].

Ce qui est intéressant ici, c'est qu'il y a potentiellement trois messages dans la file d'attente lorsque nous essayons la fonction receive {take, Smoker, AvailableMaterials} . Seulement un d'entre eux peuvent toutefois être traités maintenant . Ensuite, il y a l'intérieur receive doneSmoking comme une poignée de main. Ainsi, pour l'un, la sélection des messages appropriés qui permet au code de faire un travail tout en ne perdant pas l'autre take lors de la réception du message de poignée de main est ce qui résout tous les problèmes de concurrence ici. Si les messages non conformes/non traitables étaient abandonnés à un moment donné, la smoker resteraient bloqués pour toujours (à moins qu'ils ne répètent périodiquement leurs demandes).

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