¿Pueden los actores de Scala procesar múltiples mensajes simultáneamente?
-
06-07-2019 - |
Pregunta
La respuesta a una pregunta reciente mía indicó que un actor procesó sus mensajes uno a la vez . ¿Es esto cierto? No veo nada que diga explícitamente eso (en Programación en Scala ), que contiene el siguiente fragmento (pp. 593)
Si [el método
react
] encuentra un mensaje que puede ser manejado, [it] programará el manejo de ese mensaje para su posterior ejecución y lanzará una excepción
(El énfasis es mío). Dos preguntas relacionadas (y mutuamente excluyentes):
- Suponiendo que un actor pueda procesar múltiples mensajes de manera simulada, ¿cómo puedo obligar a un actor a procesar los mensajes 1 a la vez (si esto es lo que deseo hacer)? (usando
recibir
?) - Suponiendo que un actor procesa los mensajes uno a la vez, ¿cómo podría implementar mejor un actor que, de hecho, podría procesar mensajes simultáneamente
editar: hacer un poco de prueba parece confirmar que estoy equivocado y que los actores son secuenciales. Entonces es la pregunta # 2 que necesito responder
Solución
Los actores procesan un mensaje a la vez. El patrón clásico para procesar múltiples mensajes es tener un coordinador de frente de actor para un grupo de actores de consumo. Si usa reaccionar, el grupo de consumidores puede ser grande pero solo usará una pequeña cantidad de subprocesos JVM. Aquí hay un ejemplo en el que creo un grupo de 10 consumidores y un coordinador para que los presente.
import scala.actors.Actor
import scala.actors.Actor._
case class Request(sender : Actor, payload : String)
case class Ready(sender : Actor)
case class Result(result : String)
case object Stop
def consumer(n : Int) = actor {
loop {
react {
case Ready(sender) =>
sender ! Ready(self)
case Request(sender, payload) =>
println("request to consumer " + n + " with " + payload)
// some silly computation so the process takes awhile
val result = ((payload + payload + payload) map {case '0' => 'X'; case '1' => "-"; case c => c}).mkString
sender ! Result(result)
println("consumer " + n + " is done processing " + result )
case Stop => exit
}
}
}
// a pool of 10 consumers
val consumers = for (n <- 0 to 10) yield consumer(n)
val coordinator = actor {
loop {
react {
case msg @ Request(sender, payload) =>
consumers foreach {_ ! Ready(self)}
react {
// send the request to the first available consumer
case Ready(consumer) => consumer ! msg
}
case Stop =>
consumers foreach {_ ! Stop}
exit
}
}
}
// a little test loop - note that it's not doing anything with the results or telling the coordinator to stop
for (i <- 0 to 1000) coordinator ! Request(self, i.toString)
Este código prueba para ver qué consumidor está disponible y envía una solicitud a ese consumidor. Las alternativas son simplemente asignar aleatoriamente a los consumidores o utilizar un programador de turnos.
Dependiendo de lo que esté haciendo, podría ser mejor servido con Scala's Futures. Por ejemplo, si realmente no necesita actores, toda la maquinaria anterior podría escribirse como
import scala.actors.Futures._
def transform(payload : String) = {
val result = ((payload + payload + payload) map {case '0' => 'X'; case '1' => "-"; case c => c}).mkString
println("transformed " + payload + " to " + result )
result
}
val results = for (i <- 0 to 1000) yield future(transform(i.toString))
Otros consejos
Creo que la respuesta es que un Actor
no puede manejar los mensajes de forma asíncrona. Si tiene un Actor
que debería estar escuchando mensajes donde estos mensajes pueden manejarse de forma asíncrona , entonces podría escribirse así:
val actor_ = actor {
loop {
react {
case msg =>
//create a new actor to execute the work. The framework can then
//manage the resources effectively
actor {
//do work here
}
}
}
}
Si quieres hacer varias cosas, entonces deberías estar usando múltiples actores. Toda la razón para usar actores es dividir el trabajo entre múltiples procesos independientes.