Il y a 13 ans -
Temps de lecture 4 minutes
Devoxx – Jour 2 – Scala Actors
Le langage Scala était très représenté cette année à Devoxx avec une session sur les acteurs par Franck Sommers, une autre sur ScalaTest par Bill Venners ou encore celle sur le framework web Lift par Timothy Perrett. Cette dernière n’a malheureusement pas tenu toutes ses promesses : la moitié de la présentation s’est focalisée sur les spécificités du langage et une autre partie sur la déjà vue application ChatDemo en comet de David Pollack sans réelle explication de code, ne laissant ainsi que très peu de place pour Lift lui même.
Nous consacrerons donc un retour à Scala en deux parties : la première sur les acteurs (présent billet) et la seconde sur ScalaTest.
La présentation démarre tambour battant avec un schéma sur The evil of global state et, dans le cas du partage de variables entre plusieurs threads, l’incontournable :
int currentSpeed = 0; synchronized (currentSpeed) {...}
Tous les threads peuvent accéder à cette variable, il faut donc la synchroniser.
Le passage de messages est tout de suite introduit avec le même exemple : une vitesse et 2 threads/acteurs qui souhaitent accéder à cette variable. Mais cette fois-ci, pas de variable synchronisée mais un acteur SpeedSensor qui porte la valeur, un acteur CruiseControl qui récupère le message et exécute les actions adéquates, en l’occurrence appeler un autre acteur qui fera un ajustement de la vitesse. On se retrouve avec un exemple où tous les threads partagent la même donnée currentSpeed mais grâce à la programmation par message, il n’y a plus besoin synchroniser cette variable.
Les idées véhiculées par les acteurs peuvent alors se résumer par :
- Favoriser l’immutabilité des messages.
- Gérer une pile de messages (Mailbox).
- Gérer différentes actions selon le message entrant.
Tous ces points énumérés nous font rapidement tilt dans la tête et l’on pense évidemment au langage Scala qui porte tout à fait ces valeurs. Pour rappel, Scala est un langage qui :
- Favorise l’immutabilité des objets.
- Est orienté objet + orienté fonction.
- Promeut le pattern matching (un switch sous stéroïdes selon le speaker :) qui permet différentes actions selon le type de pattern correspondant).
- Permet le DSL (écriture plus lisible d’API).
- Tourne sur une JVM donc intégralement compatible Java.
Il ne faut pas oublier qu’en outre les acteurs ne sont pas intégrés au langage Scala, ce n’est qu’une API qui est fournie avec Scala. L’écriture des appels fait croire à de la syntaxe propre au langage mais ce n’est qu’un DSL qui invoque de simples méthodes. ScalaTest vous montrera (d’ici quelques jours) la puissance de ces DSLs et de ce qu’il est possible de faire avec. D’ailleurs, si l’implémentation des acteurs ne vous plaît pas, vous pouvez tout à fait les refaire à votre façon (et à vos risques et périls ;))
La déclaration d’un acteur en Scala se fait en étendant le trait Actor
et en redéfinissant la méthode def act()
. La définition des messages peut se faire par case class
ce qui permet de voir rapidement tous les types de messages disponibles pour nos acteurs et ainsi effectuer un pattern matching lisible :
case class Subscribe(user : User) case class Unsubscribe(user: User) ... msg match { case Subscribe(user) => println(user + "subscribed") case Unsubscribe(user) => println(user + "unsubscribed") }
L’envoi de message se fait de différentes manières :
- Asynchrone :
chatRoom ! Subscribe(User("Foo"))
, non bloquant, retour immédiat. - Synchrone :
var result = chatRoom !? Subscribe(User("Bar"))
, bloquant, en attente du retour de la méthode. - Future :
var future = chatRoom !! Subscribe(User("Baz"))
, non bloquant, en attente du retour de la méthode.
Vient ensuite la partie haute performance des acteurs. Franck nous présente ainsi les Remote actors ou acteurs distribués. Ces acteurs ont la particularité de dialoguer entre eux même s’ils sont sur différentes machines physiques. La création d’un acteur remote se fait grâce à l’appel à alive
(qui crée une socket et la bind sur le port spécifié) et à register
:
alive(9090) register('chatRoom', self)
L’envoi de message se fait en récupérant un proxy sur le nœud courant, l’appel à select
permettra de récupérer l’acteur dont l’alias a été enregistré par le register
(voir ci-dessus). L’acteur remote aura la bonne référence au niveau du sender (référence à celui qui a envoyé le message) :
val remoteActor = select(Node(, 9090), 'chatRoom') remote ! Post("Foo=Bar")
La partie scalabilité approche à grand pas et le speaker évoque à présent les deux manières de démarrer nos acteurs :
- Soit un thread par acteur : grosse consommation mémoire et très vite limité par la JVM (plusieurs milliers).
- soit event-driven : découplage des acteurs et des threads jvm, exécuté sur un thread pool, attente du message sans consommer de thread ! On parle ici en millions d’acteurs !
Vous trouverez un exemple d’actors in action fourni par le speaker à cette url ainsi qu’un très bon article sur les acteurs remote.
Commentaire