À côté de ces concepts élémentaires pour la programmation parallèle, Erlang met à notre disposition quelques outils pour nous faciliter la tâche. Le premier est plus ou moins un gadget qui permet de nommer des processus.
Nommer les processus
Erlang nous laisse associer un nom de notre choix à un PID, qui nous permettra de rendre plus lisible notre code. La fonction standard
register/2, qui prend en argument un atome et un PID, rend global un identifiant qui peut être utilisé à la place du PID du processus, et ce dans n'importe quelle partie du code.
Vous pouvez facilement adapter le code précédent pour qu'il ne soit plus nécessaire de passer en argument du client l'identifiant du processus : les deux premières fonctions sont maintenant
Code : Erlang 1
2
3
4
5
6
7
8
9
10
11
12 | start() ->
PID = spawn(?MODULE, pongueur, [0]),
register(serveur, PID),
spawn(?MODULE, pingueur, []).
pingueur() ->
serveur ! {ping, self()},
receive
{pong, N} ->
io:format("Reçu ~w~n", [N])
end,
pingueur(). %% On boucle
|
Si vous lancez le code depuis la ligne de commande, vous pourrez cette fois-ci envoyer un message au processus serveur en écrivant
server ! {ping, self()}.. Vous n'aurez pas la réponse du serveur, mais un délai supplémentaire de deux secondes entre deux affichages du message du client, car le serveur aura bien été occupé momentanément par ce nouveau client

.
Vous pouvez utiliser
registered/0 pour récupérer la liste des processus nommés localement. Un nom est oublié à l'aide de
unregister/1, ou lorsque le processus se termine. Enfin,
whereis/1 fait correspondre au nom passé en argument l'éventuel PID auquel il correspond, ou
undefined sinon.
Les liens
Comme je l'ai dit, si jamais
spawn ne réussit pas à exécuter un processus, le code se déroule quand même comme si de rien n'était (si vous essayez à l'aide de la ligne de commande, vous aurez un
rapport d'erreur, mais pas pour autant de véritable erreur, au sens de celles que nous verrons au chapitre 5). En fait, le processus fils est lancé indépendamment du processus père, il vit en quelques sortes sa vie. Mais on peut s'arranger pour que les deux gardent contact, à l'aide de la fonction
spawn_link/3, qui prend les mêmes arguments que
spawn, ou bien de la fonction
link, qui elle,
utilisée à l'intérieur d'un processus X, va relier X au processus précisé en argument.
Lier des processus en Erlang nous permettra par la suite de traiter efficacement les erreurs des processus fils. Pour l'instant, je ne vais présenter que l'interception du message de sortie qui est produit lorsqu'un processus se termine. Il est bien sûr inutile que le processus serveur tourne indéfiniment alors que le processus client est arrêté. Notre première solution pour éviter cette situation a été d'utiliser
after, mais on peut à la place utiliser un lien, qui présente l'avantage de ne pas placer de délai arbitraire dans le code.
On va donc, au lancement du processus de ping, relier les deux processus entre eux. On va également rajouter la possibilité pour le client de recevoir en message l'atome
exit que nous enverrons depuis la ligne de commande, ce qui modifie un peu le début de notre code. La nouvelle fonction peut surprendre, c'est juste un détail :
pingueur est exécutée plusieurs fois, il est inutile de recréer à chaque fois un lien à l'aide de
link, donc on sépare l'utilisation de link du code répété.
Code : Erlang 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | -module(pingpong3).
-export([start/0, pingueur_start/0, pongueur/1]).
%% Fonction qui va lancer nos deux processus avec le nombre initial
start() ->
PID = spawn(?MODULE, pongueur, [0]),
register(serveur, PID),
spawn(?MODULE, pingueur_start, []).
pingueur_start() ->
link(whereis(serveur)), %% On utilie whereis car link attend un PID.
pingueur().
pingueur() ->
serveur ! {ping, self()},
receive
{pong, N} ->
io:format("Reçu ~w~n", [N]),
pingueur(); %% On boucle
exit ->
ok %% Si on reçoit un message 'exit', on arrête tout
end.
|
Que se passe-t-il lorsqu'un processus A se termine ? Selon qu'il termine sur une erreur ou sur un autre résultat (nous en reparlerons au prochain chapitre), la situation n'est pas la même. Dans le premier cas, un message d'erreur est propagé à tous les processus auxquels A est relié, et ces processus terminent également, sauf si une certaine option (propre à chaque processus) est réglée à
true ; cette option, qui s'appelle
trap_exit, intervient dans le deuxième cas : si le processus A termine sur autre chose qu'une erreur, alors
les processus liés à A ne reçoivent un message que si l'option trap_exit est réglée sur true.
C'est peut-être un peu flou pour l'instant, mais nous en reparlerons très prochainement. Pour l'instant, contentons nous de comprendre que si nous réglons l'option
trap_exit, alors lorsque le processus de ping terminera, un message particulier sera envoyé au processus de pong. Sans plus de commentaires, voici le code correct :
Code : Erlang 1
2
3
4
5
6
7
8
9
10
11 | pongueur(N) ->
process_flag(trap_exit, true),
timer:sleep(2000), %% Sert à attendre deux secondes (pour que les choses n'aillent pas trop vite)
receive
{ping, PID} -> %% Un ping envoyé par PID
M = N + 1, %% Un message de plus
PID ! {pong, M},
pongueur(M); %% On boucle avec le nouveau nombre de messages reçus.
{'EXIT', PID, Raison} ->
io:format("Le serveur s'arrête !~n")
end.
|
Notez le réglage de l'option, ainsi que le motif correspondant au message spécial de fin de processus. C'est un mécanisme utile qui nous permet de synchroniser un minimum nos processus, en leur donnant la possibilité de connaître l'état (vivant ou mort) de leurs voisins.
Lorsque Erlang a commencé à être utilisé sur des réseaux de grande taille, pour de la production industrielle, il devenait important de distinguer le code de gestion des erreurs du reste. Afin de surveiller l'état du réseau, le code de gestion devait avoir conscience des problèmes qui pouvaient survenir. Le mécanisme de liens nous donne l'opportunité de propager facilement des informations en cas de pépin : c'est une notion très importante en Erlang. Nous serons donc amené à la recroiser.