Concept
Le modèle d’acteur est un paradigme de programmation concurrente et distribuée, introduit par Carl Hewitt en 1973, où les entités fondamentales sont appelées acteurs. Chaque acteur est une unité indépendante qui encapsule son état et son comportement, et qui interagit avec d’autres acteurs uniquement via l’envoi de messages asynchrones.
Un acteur, en réponse à la réception d’un message, peut :
- effectuer un traitement local,
- envoyer des messages à d’autres acteurs,
- créer de nouveaux acteurs,
- spécifier son comportement pour la prochaine réception de message
---
config:
theme: dark
---
flowchart LR
i@{ shape: braces, label: "in_msg1
...
in_msgN" }
io1@{ shape: lin-cyl, label: "I/O" }
io2@{ shape: cloud, label: "I/O" }
subgraph Actor
f["Behaviour"]
mb["Mailbox"]@{shape: docs}
state["State"]@{shape: cyl}
end
f-.->io1
f-.->io2
f-.->|spawn|actorX
f-.->|send|actorY
i-->mb-- receive -->f
f-.->state
Principes
Tout est un acteur
Dans la programmation objet, tout est objet. Dans la programmation fonctionnelle, tout est fonction. Dans le modèle d’acteur, tout est acteur. Même votre terminal deviendra un acteur !
Comme dans les deux autres paradigmes, on essaye d’appliquer le Single Responsability Principle et de faire en sorte qu’un acteur n’ai qu’un seul objectif.
Pas d’état partagé
Les acteurs ne partagent pas d’état : toute communication passe par des messages, ce qui élimine les problèmes liés à la mémoire partagée et aux verrous (lock, mutex, semaphore, etc.).
Communication par messages asynchrones
Au sein d’un acteur, tout est procédural. Le seul moyen de déroger à cette règle est en envoyant des messages. Par exemple, je peux envoyer des messages à plusieurs acteurs afin de paralléliser certaines choses.
Traitement séquentiel des messages
De manière générale, un acteur boucle sur les messages de sa boîte aux lettres et traite les messages l’un après l’autre.
Info : Il est possible de déroger à cette règle comme dans cet exemple : Selective receive.
Location transparency
Un acteur peut envoyer des messages à un autre acteur sans savoir si celui-ci est sur la même machine ou sur une autre. Le code reste identique quelle que soit la localisation physique de l’acteur destinataire. Ce principe favorise donc la mise à l’échelle du système. La plomberie est gérée par la machine virtuelle BEAM elle-même.
Isolation des erreurs
C’est le plus important de tous. Erlang promeut le Let it crash. C’est ce principe qui, accompagné de supervision, permet d’avoir un système résilient (tolérant aux pannes).
Si un acteur s’arrête pour une raison ou pour une autre, l’impact est contrôlé et il est souvent facile de le relancer à partir d’un état acceptable. En effet, un acteur peut monitor un autre acteur et on verra qu’il existe en Erlang un framework tellement omniprésent que le langage a été renommé pour l’intégrer : Erlang/OTP.
C’est grâce à cela que l’on ne programme pas défensivement et qu’on laisse les acteurs exit délibérément.
Conclusion
Le modèle d’acteur est né bien avant la naissance des processeurs avec plusieurs cœurs. Mais lorsque ceux-ci sont sortis, une simple mise à jour de la machine virtuelle a vu tous les programmes existants accélérer sans aucune modification. Programmer pour du multi-core ou pour du multi-machine est identique avec ce modèle !
Il est donc un paradigme très intéréssant dès lors que l’on cherche à faire un système distribué de type soft-realtime. Le paradigme excele quand il se passe beaucoup de choses en parallèle.
Je précise soft-realtime car bien qu’on gère beaucoup de choses à la fois, il n’y a pas de garantie sur le temps que prendrons les choses. Les langages qui implémente le paradigme sont souvent basé sur des machines virtuelles (BEAM, JVM) et n’auront jamais les performances d’un langage compilé.
Personnellement je trouve qu’il s’applique aussi très bien aux systèmes monolithiques.