Modelando el mundo con Actores

Hoy vamos a hablar sobre los actores. Muchos de vosotros estaréis pensando en Brad Pitt o Javier Bardem… Alguno habrá que haya pensado en sistemas reactivos, en programación basada en futuros, algunos incluso en Quasar o Akka (según si sois java-tos o scala-dores).

En este post vamos a hablar un poco del modelo de programación basado en actores y su gran potencial en sistemas distribuidos.

Pero… ¿qué es un actor?

El modelo de actores fue descrito por primera vez en 1973 por Carl Hewitt, como un modelo de concurrencia computacional que, al igual que los hilos, trata de solucionar el problema de la concurrencia.

El planteamiento es muy sencillo. Se definen objetos, llamados actores, que disponen de dos elementos: una cola o buzón de mensajería, en el que almacenar los mensajes o tareas que se le asignan; y un comportamiento, que incluye la lista de operaciones a realizar en función del mensaje recibido.

Además de realizar modificaciones sobre su estado, un actor puede:

  • Enviar mensajes a otros actores, incluso redireccionar el mensaje recibido.
  • Crear nuevos actores.
  • Modificar su comportamiento para los próximos mensajes.

En cuanto a la comunicación entre actores, y la razón por la que este modelo se aplica en la resolución de problemas de concurrencia, es la asincronía. Es decir, el remitente del mensaje no espera recibir respuesta tras realizar el envío, y sigue con su ejecución.

Este modelo de mensajería asíncrona, sirve de base para sistemas reactivos con especialización de elementos, lo que se potencia por el hecho de no existir un estado compartido entre los actores. Si se desea conocer el estado interno de algún actor, se deberá solicitar la información enviándole un mensaje.

Aplicando el Taylorismo a la informática

A principios del siglo XX, Frederick W. Taylor sentó las bases de un sistema de organización racional del trabajo, basado en la división sistemática de las tareas y la distribución de cada subtarea para que sea realizada por trabajadores especializados en dicha tarea. Y este sistema teórico fue pronto puesto en práctica por Ransom Olds en 1901 de una manera bastante básica, y posteriormente, con una capacidad de producción muy superior, por Henry Ford para la creación del Ford T.

Analizando los principios descritos por Taylor, división de la tarea completa en subtareas mínimas e indivisibles, distribución de subtareas para paralelizar los esfuerzos y aplicación de elementos especializados para la consecución de cada subtarea; es un ejemplo claro de modelo de actores con actores especializados, en los que cada actor es capaz de realizar una serie de tareas específicas y que confía en el resto de actores de la cadena para que realicen su trabajo de la manera más eficaz y eficiente, sin necesitar supervisar su trabajo ni si han terminado o no de realizar sus tareas.

Pongamos un ejemplo

Siguiendo con esa ficticia cadena de montaje manual de coches, el trabajador encargado de poner el volante del coche no necesita saber que todos los demás trabajadores han terminado su parte del trabajo y el coche ha sido terminado antes de empezar a poner el volante al siguiente coche.

Incluso, hilando más fino, en la parte de la cadena en la que se ponen las cuatro ruedas, hay cuatro personas, llamémosles trabajadores, especializados en poner una rueda cada uno; y una quinta persona, llamémosle supervisor, que autoriza el avance del vehículo.

Al llegar un vehículo, los cuatro trabajadores se ponen manos a la obra y cada uno pone una rueda, avisa al supervisor de que ha terminado y prepara todo a la espera de que llegue el siguiente coche.

El supervisor por su parte espera a recibir la confirmación de los cuatro trabajadores para después autorizar el avance del coche al siguiente paso de la cadena y así recibir el siguiente coche.

El código simplificado en Scala + Akka de los dos actores que formarían parte en este ejemplo sería algo parecido a esto:

1
2
3
4
5
6
7
8
9
class Trabajador(supervisor: ActorRef) extends Actor {
   def receive = {
      case PonerRueda =>
         // Pongo la rueda
         ponerRueda()
         // Aviso al supervisor de que he terminado
         supervisor ! TrabajoFinalizado
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Supervisor(siguienteSupervisor: ActorRef) extends Actor {
   val trabajadores: List[Trabajador]
   val numeroRuedas: Int = 4
   var contador: Int = 0
   def receive = {
      case CochePreparado =>
         // El coche está listo, así que aviso a los trabajadores para que
         // cada uno ponga una rueda.
         trabajadores.foreach {
            trabajador => trabajador ! PonerRueda
         }
      case TrabajoFinalizado =>
         // Un trabajador ha terminado.
         // Lo contabilizo para saber cuándo se ha terminado el trabajo completo.
         contador = contador + 1
         if (contador == numeroRuedas) {
            // Si he contabilizado cuatro avisos, quiere decir que se han puesto
            // las ruedas necesarias y el trabajo ha finalizado.
            // Aviso al siguiente supervisor para que sepa que el coche está listo
            // para que realicen otras tareas sobre él.
            siguienteSupervisor ! CochePreparado
            // Reinicio el contador de trabajo.
            contador = 0
         }
   }
}
NOTA: En este ejemplo se ha usado el modelo que ofrece Akka para actores. En Akka, la expresión ‘X ! Y’, correspondiente al método tell de los actores de Akka, indica que se está diciendo al actor X el mensaje Y, lo que se traduce en que el mensaje Y irá al buzón de mensajes del actor X y, cuando le llegue el turno, el actor X ejecutará el comportamiento asociado a dicho mensaje Y. Es el modelo conocido como fire-and-forget dado que quien llama al actor X no se queda esperando una respuesta, ni positiva ni negativa, al mensaje enviado y continúa su ejecución.

Como se puede ver, la utilización de este modelo, además de resolver problemas de concurrencia, permite crear elementos con una funcionalidad muy definida, clara y concisa; y es la unión de múltiples de estos elementos lo que permite crear sistemas capaces de resolver problemas de muy alta complejidad.

Cabe destacar que en este ejemplo no se está cambiando el comportamiento del actor pero, para asegurar la estabilidad del supervisor en un entorno exigente, su comportamiento debería cambiar al recibir el mensaje de que hay un coche esperando (CochePreparado) para evitar que reciba más mensajes de ese tipo y sólo espere mensajes de confirmación de trabajo realizado. O también podría escalar internamente el número de trabajadores, creando nuevos actores de tipo Trabajador para poder aceptar más coches en su punto de montaje. O también…

Resumiendo, ¡el límite de los actores es nuestra imaginación! ¿Cómo los usarías tú?

Mi abuelo me dijo una vez: “Nunca te acostarás sin saber una cosa más” , desde entonces esa frase ha marcado siempre mi día a día. Ese afán autodidacta, unido a las ganas de obtener el mejor resultado de todo lo que hago, me ha llevado a desarrollar una apasionante carrera en el mundo de las tecnologías de análisis de datos y a formar parte del equipo de innovación tecnológica de FutureSpace como Especialista Big Data.