Permítanme decir primero que tengo mucha experiencia en Java, pero solo recientemente me he interesado en los lenguajes funcionales. Recientemente comencé a buscar en Scala, que parece un lenguaje muy agradable.
Sin embargo, he estado leyendo sobre el marco Actor de Scala en Programación en Scala , y hay una cosa que no entiendo. En el capítulo 30.4 dice que usar en react
lugar de receive
permite reutilizar subprocesos, lo cual es bueno para el rendimiento, ya que los subprocesos son costosos en la JVM.
¿Significa esto que, siempre que me acuerde de llamar en react
lugar de receive
, puedo iniciar tantos Actores como quiera? Antes de descubrir Scala, he estado jugando con Erlang, y el autor de Programming Erlang se jacta de generar más de 200.000 procesos sin sudar. Odiaría hacer eso con los hilos de Java. ¿Qué tipo de límites estoy mirando en Scala en comparación con Erlang (y Java)?
Además, ¿cómo funciona la reutilización de este hilo en Scala? Supongamos, por simplicidad, que solo tengo un hilo. ¿Todos los actores que empiezo se ejecutarán secuencialmente en este hilo, o se producirá algún tipo de cambio de tarea? Por ejemplo, si comienzo a dos actores que se envían mensajes de ping-pong entre sí, ¿correré el riesgo de un punto muerto si comienzan en el mismo hilo?
Según Programming in Scala , escribir actores para usar react
es más difícil que con receive
. Esto suena plausible, ya react
que no regresa. Sin embargo, el libro continúa mostrando cómo se puede poner react
dentro de un bucle usando Actor.loop
. Como resultado, obtienes
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 algunos receive
". Entonces, ¿qué me estoy perdiendo aquí? ¿Qué puede receive
hacer que react
no pueda, además de regresar? ¿Y por qué me importa?
Finalmente, llegando al núcleo de lo que no entiendo: el libro sigue mencionando cómo el uso 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é se puede descartar la pila de llamadas cuando una función termina lanzando una excepción ( react
), pero no cuando termina devolviendo ( receive
)?
Tengo la impresión de que Programming in Scala ha pasado por alto algunos de los problemas clave aquí, lo cual es una pena, porque de lo contrario es un libro realmente excelente.
fuente
Respuestas:
Primero, cada actor que espera
receive
ocupa un hilo. Si nunca recibe nada, ese hilo nunca hará nada. Un actor enreact
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 de recepción devuelva algo, un hilo de reacción no lo es. Por lo tanto, el estado de la pila anterior al final del último
react
puede descartarse por completo. No es necesario guardar ni restaurar el estado de la pila, lo que acelera el inicio del hilo.Hay varias razones de rendimiento por las que podría querer una u otra. Como sabe, tener demasiados hilos en Java no es una buena idea. Por otro lado, debido a que debe adjuntar un actor a un hilo antes de que pueda
react
, es más rápido conreceive
un mensaje quereact
con él. Por lo tanto, si tiene actores que reciben muchos mensajes pero hacen muy poco con ellos, el retraso adicionalreact
puede hacer que sea demasiado lento para sus propósitos.fuente
La respuesta es "sí": si sus actores no están bloqueando nada en su código y usted lo está usando
react
, entonces puede ejecutar su programa "concurrente" dentro de un solo hilo (intente configurar la propiedad del sistemaactors.maxPoolSize
para averiguarlo).Una de las razones más obvias por las que es necesario descartar la pila de llamadas es que, de lo contrario, el
loop
método terminaría en aStackOverflowError
. Tal como están las cosas, el marco termina inteligentemente areact
lanzando aSuspendActorException
, que es capturado por el código de bucle que luego se ejecuta dereact
nuevo a través delandThen
método.Eche un vistazo al
mkBody
método enActor
y luego alseq
método para ver cómo el ciclo se reprograma a sí mismo: ¡cosas terriblemente inteligentes!fuente
Esas declaraciones de "descartar la pila" me confundieron también por un tiempo y creo que lo entiendo ahora y este es mi entendimiento ahora. En caso de "recibir" hay un bloqueo de hilo dedicado en el mensaje (usando object.wait () en un monitor) y esto significa que la pila de hilo completa está disponible y lista para continuar desde el punto de "esperar" al recibir un mensaje. Por ejemplo, si tuviera el siguiente código
el hilo esperaría en la llamada de recepción hasta que se reciba el mensaje y luego continuaría e imprimiría el mensaje "después de recibir e imprimir un 10" y con el valor de "10" que está en el marco de la pila antes de que el hilo se bloquee.
En caso de reaccionar no hay tal hilo dedicado, todo el cuerpo del método del método de reacción se captura 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 aquellas declaraciones que se puedan capturar como un cierre y ahí es donde entra en juego el tipo de retorno de "Nada". Considere el siguiente código
Si react tuviera un tipo de retorno de void, significaría que es legal tener declaraciones después de la llamada "react" (en el ejemplo, la declaración println que imprime el mensaje "después de reaccionar e imprimir un 10"), pero en realidad eso nunca se ejecutaría, ya que solo el cuerpo del método "react" se captura y se secuencia para su ejecución más tarde (a la llegada de un mensaje). Dado que el contrato de react tiene el tipo de retorno de "Nada", no puede haber ninguna declaración 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 posteriores a las llamadas de reacción no se ejecutan en absoluto. Tenga en cuenta que todas las variables necesarias por el cuerpo de react ya se capturaron como un cierre, por lo que se puede ejecutar sin problemas.
El marco del actor Java Kilim en realidad hace el mantenimiento de la pila al guardar la pila que se desenrolla en la reacción y recibe un mensaje.
fuente
+a
en los fragmentos de código, en lugar de+10
?Solo para tenerlo aquí:
Programación basada en eventos sin inversión de control
Estos artículos están vinculados desde la scala api para Actor y proporcionan el marco teórico para la implementación del actor. Esto incluye por qué es posible que la reacción nunca regrese.
fuente
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 grupo de hilos inteligente que es la ejecución de actores en el tiempo ... Cada segmento será una ejecución de mensaje hasta su finalización por parte de un actor a diferencia de Erlang, que podría ser por instrucción.
Esto me lleva a pensar que reaccionar es mejor, ya que sugiere que el hilo actual considere a otros actores para la programación donde, como recibir, "podría" involucrar el hilo actual para continuar ejecutando otros mensajes para el mismo actor.
fuente