2009-08-09 18 views
110

Permítanme decir primero que tengo mucha experiencia en Java, pero que recientemente me interesaron los lenguajes funcionales. Recientemente comencé a buscar en Scala, que parece ser un lenguaje muy agradable.actores Scala: recibir vs reaccionar

Sin embargo, he estado leyendo sobre el framework Actor de Scala en Programming in Scala, y hay una cosa que no entiendo. En el capítulo 30.4 dice que usar react en lugar de receive hace posible reutilizar hilos, lo que es bueno para el rendimiento, ya que los hilos son caros en la JVM.

¿Esto significa que, siempre que recuerde llamar al react en lugar de receive, puedo comenzar tantos actores como quiera? Antes de descubrir a Scala, he estado jugando con Erlang, y el autor de Programming Erlang se jacta de engendrar más de 200,000 procesos sin sudar nada. Odiaría hacer eso con los hilos de Java. ¿Qué tipo de límites estoy viendo en Scala en comparación con Erlang (y Java)?

Además, ¿cómo funciona este subproceso de reutilización en Scala? Supongamos, por simplicidad, que tengo solo un hilo. ¿Todos los actores que empiezo se ejecutarán secuencialmente en este hilo, o se producirá algún tipo de cambio de tareas? Por ejemplo, si inicio dos actores con mensajes de ping-pong entre sí, ¿me arriesgaré a un punto muerto si se inician en el mismo hilo?

Según Programación en Scala, escribiendo actores a utilizar react es más difícil que con receive. Esto suena plausible, ya que react no regresa. Sin embargo, el libro continúa para mostrar cómo puede poner un react dentro de un bucle usando Actor.loop. Como resultado, se obtiene

loop { 
    react { 
     ... 
    } 
} 

que, para mí, parece bastante similar a

while (true) { 
    receive { 
     ... 
    } 
} 

que se utiliza anteriormente en el libro. Aún así, el libro dice que "en la práctica, los programas necesitarán al menos unos pocos receive" s ". Entonces, ¿qué es lo que echo de menos aquí? ¿Qué puede hacer receive que react no puede, además de devolver? ¿Y por qué me importa?

Finalmente, llegando al núcleo de lo que no entiendo: el libro sigue mencionando cómo usar react hace posible descartar la pila de llamadas para reutilizar el hilo. ¿Cómo funciona? ¿Por qué es necesario descartar la pila de llamadas? ¿Y por qué la pila de llamadas puede descartarse cuando una función termina lanzando una excepción (react), pero no cuando finaliza al devolver (receive)?

Tengo la impresión de que Programación en Scala ha estado pasando por alto algunos de los problemas clave aquí, lo cual es una pena, porque de lo contrario es un libro realmente excelente.

+2

ver también http://stackoverflow.com/questions/1526845/when-are-threads-created-for-scala-actors –

Respuesta

77

Primero, cada actor esperando en receive está ocupando un hilo. Si nunca recibe nada, ese hilo nunca hará nada. Un actor en react no ocupa ningún hilo hasta que recibe algo. Una vez que recibe algo, se le asigna un hilo y se inicializa en él.

Ahora, la parte de inicialización es importante. Se espera que un hilo receptor devuelva algo, un hilo que reacciona no lo es. Por lo tanto, el estado de pila anterior al final de la última react se puede descartar completamente.No es necesario guardar ni restaurar el estado de pila, lo que hace que el subproceso sea más rápido para comenzar.

Existen varios motivos de rendimiento por los que podría querer uno u otro. Como saben, tener demasiados hilos en Java no es una buena idea. Por otro lado, como tiene que adjuntar un actor a un hilo antes de que pueda react, es más rápido que receive un mensaje que react. Por lo tanto, si tiene actores que reciben muchos mensajes pero hacen muy poco con ellos, la demora adicional de react puede hacer que sea demasiado lento para sus propósitos.

20

La respuesta es "sí" - si los actores no están bloqueando en nada en su código y está utilizando react, a continuación, puede ejecutar su programa de "concurrente" dentro de un solo hilo (prueba a configurar la propiedad del sistema actors.maxPoolSize descubrir).

Una de las razones más obvias por las que es necesario desechar la pila de llamadas es que de lo contrario el método loop terminaría en una StackOverflowError. Tal como está, el marco acaba ingeniosamente con un react lanzando un SuspendActorException, que es capturado por el código de bucle que luego ejecuta el react nuevamente a través del método andThen.

Tenga una mirada en el método mkBody en Actor y luego el método seq para ver cómo el bucle vuelve a programar en sí - cosas terriblemente inteligente!

19

Esas declaraciones de "descartar la pila" me confundieron también por un tiempo y creo que ahora lo entiendo y ahora lo entiendo. En el caso de "recibir" hay un bloqueo de hilos dedicado en el mensaje (usando object.wait() en un monitor) y esto significa que la pila de hilos completa está disponible y lista para continuar desde el punto de "espera" al recibir un mensaje. Por ejemplo, si tiene el siguiente código

def a = 10; 
    while (! done) { 
    receive { 
     case msg => println("MESSAGE RECEIVED: " + msg) 
    } 
    println("after receive and printing a " + a) 
    } 

el hilo podría esperar en la llamada de recepción hasta que se recibe el mensaje y luego continuaría e imprimir el "después de recibir e imprimir un 10" mensaje y con el valor de "10" que está en el marco de pila antes de que se bloquee el hilo.

En caso de reacción no existe tal thread dedicado, todo el método de cuerpo del método de reacción es capturado como un cierre y es ejecutado por algún hilo arbitrario en el actor correspondiente que recibe un mensaje. Esto significa que solo se ejecutarán las declaraciones que se pueden capturar como cierre solo y ahí es donde entra en juego el tipo de devolución de "Nada". Considere el siguiente código

def a = 10; 
    while (! done) { 
    react { 
     case msg => println("MESSAGE RECEIVED: " + msg) 
    } 
    println("after react and printing a " + a) 
    } 

Si reaccionar tenían un tipo de retorno de vacío, significaría que es legal tener declaraciones después de que el "reaccionan" llamada (en el ejemplo la sentencia println que imprime el mensaje "después de reaccionar e imprimiendo un 10 "), pero en realidad eso nunca sería ejecutado ya que solo el cuerpo del método de" reacción "es capturado y secuenciado para su posterior ejecución (a la llegada de un mensaje). Dado que el contrato de reacción tiene el tipo de devolución de "Nada", no puede haber ningún enunciado después de reaccionar, y no hay razón para mantener la pila. En el ejemplo anterior, la variable "a" no tendría que mantenerse ya que las declaraciones después de las llamadas de reacción no se ejecutan en absoluto. Tenga en cuenta que todas las variables necesarias por el cuerpo de reacción ya se capturan como un cierre, por lo que puede ejecutarse bien.

El framework java actor Kilim realmente hace el mantenimiento de la pila al guardar la pila que se desenrolla en la reacción de recibir un mensaje.

+0

Gracias, eso fue muy informativo. ¿Pero no te refieres a '+ a' en los fragmentos de código, en lugar de' + 10'? – jqno

+0

Gran respuesta. Yo tampoco entiendo eso. – santiagobasulto

8

sólo para tener aquí:

Event-Based Programming without Inversion of Control

Estos documentos están vinculados a la API Scala de actor y proporcionan el marco teórico para la aplicación actor. Esto incluye por qué reaccionar nunca puede regresar.

+0

Y el segundo documento. Control de correo no deseado malo :( [Actores que unifican hilos y eventos] [2] [2]: http://lamp.epfl.ch/~phaller/doc/haller07coord.pdf "Actores que unifican hilos y eventos " – Hexren

0

No he hecho ningún trabajo importante con scala/akka, sin embargo, entiendo que hay una diferencia muy significativa en la forma en que se programan los actores. Akka es solo un subproceso inteligente que es la ejecución en tiempo de ejecución de los actores ... Cada vez que el corte será un mensaje de ejecución para completar por un actor a diferencia de Erlang que podría ser por instrucción?

Esto me lleva a pensar que reaccionar es mejor, ya que insinúa el hilo actual para considerar a otros actores para programar dónde recibir "poder" involucrar al hilo actual para continuar ejecutando otros mensajes para el mismo actor.