Introdução ao Modelo de Atores
O Scala oferece um modelo de concorrência baseado em atores, onde cada ator é uma entidade semelhante a uma thread que possui uma caixa de mensagens privada. Os atores se comunicam exclusivamente através de mensagens assíncronas, eliminando a necessidade de gerenciamento explícito de locks e sincronização.
- Utilização Básica de Atores
O Scala gerencia um pool de threads que é compartilhado por todos os atores do sistema. Existem duas abordagens principais para o processamento de mensagens:
O modelo receive remove uma thread do pool e a utiliza exclusivamente até que o processamento seja concluído. Já o modelo react reutiliza a thread após o processamento, permitindo que outro ator a utilize, o que resulta em melhor eficiência.
Exemplo 1: Definição de Ator via Herança
import scala.actors.Actor
class AtorSimples extends Actor {
def act() {
for (i <- 1 to 5) {
println("Execução número: " + i)
Thread.sleep(1000)
}
}
}
object ExemploAtor {
def main(args: Array[String]): Unit = {
val ator = new AtorSimples()
ator.start()
}
}
Saída:
Execução número: 1 Execução número: 2 Execução número: 3 Execução número: 4 Execução número: 5
Exemplo 2: Criação de Ator com Método factory
import scala.actors.Actor
object ExemploAtor2 {
def main(args: Array[String]): Unit = {
Actor.actor {
for (i <- 1 to 5) {
println("Outro Ator " + i)
Thread.sleep(1000)
}
}
}
}
Saída:
Outro Ator 1 Outro Ator 2 Outro Ator 3 Outro Ator 4 Outro Ator 5
- Comunicação entre Atores
Operadores de Envio de Mensagens
| Operador | Descrição |
|---|---|
| ! | Envia mensagem assíncrona sem retorno. |
| !? | Envia mensagem síncrona e bloqueia até receber resposta. |
| !! | Envia mensagem assíncrona retornando Future[Any]. |
| ? | Sem parâmetros. Retorna próxima mensaegm da caixa. |
- Recebimento de Mensagens
3.1 Receive: Processamento Repetido
O receive mantém a thread ocupada enquantoprocessa mensagens. Para processar múltiplas mensagens, utiliza-se um loop externo.
import scala.actors.Actor
val atorProcessador = Actor.actor {
var ativo = true
while (ativo) {
Actor.receive {
case numero: Int => println("Recebi inteiro: " + numero)
case texto: String => println("Recebi texto: " + texto)
case _ => println("Tipo de mensagem não reconhecido")
}
}
}
atorProcessador ! 42
atorProcessador ! 100
atorProcessador ! 3.14
Saída:
Recebi inteiro: 42 Recebi inteiro: 100 Tipo de mensagem não reconhecido
3.2 React: Reutilização de Threads
O react nunca retorna e permite reutilização de threads, sendo mais eficiente que receive. Utiliza-se loop para processar mensagens repetidamente.
import java.net.InetAddress
import java.net.UnknownHostException
import scala.actors.Actor
object ServicoDNS {
def main(args: Array[String]): Unit = {
ResolvedorNome.start()
ResolvedorNome ! ("www.google.com", Actor.self)
ResolvedorNome ! "mensagem_teste1"
ResolvedorNome ! "SAIR"
ResolvedorNome ! "mensagem_teste2"
}
}
object ResolvedorNome extends Actor {
def act() {
loop {
react {
case (nome: String, ator: Actor) =>
println(obterIP(nome))
case "SAIR" =>
println("Resolvedor encerrando...")
exit()
case msg =>
println("Mensagem não tratada: " + msg)
}
}
}
def obterIP(nome: String): Option[InetAddress] = {
try {
Some(InetAddress.getByName(nome))
} catch {
case _: UnknownHostException => None
}
}
}
Saída:
Some(www.google.com/142.250.190.46)
Mensagem não tratada: mensagem_teste1
Resolvedor encerrando...
- Práticas Recomendadas
4.1 Evitar Bloqueio em Atores
Atores não devem bloquear durante o processamento de mensagens. Quando um ator bloqueia, ele não consegue responder a outras requisições, podendo causar deadlock em cenários com múltiplos atores.
object DemoNaoBloqueante {
def executarTarefaAssincrona(atorPrincipal: Actor) {
Actor.actor {
Thread.sleep(2000)
atorPrincipal ! "TarefaConcluida"
}
}
def main(args: Array[String]): Unit = {
val ator = Actor.actor {
var execucoes = 0
var continuar = true
executarTarefaAssincrona(Actor.self)
Actor.loopWhile(continuar) {
Actor.react {
case "TarefaConcluida" =>
println("Executando operação " + execucoes)
execucoes += 1
if (execucoes < 3) {
executarTarefaAssincrona(Actor.self)
} else {
continuar = false
}
case msg =>
println("Recebido: " + msg)
}
}
}
}
}
Saída:
Executando operação 0 Executando operação 1 Executando operação 2
4.2 Comunicação Exclusiva por Mensagens
Atores devem comunicar-se apenas através de mensagens, nunca compartilhando referências diretas. Compartilhar referências permite acesso concorrente a dados mutáveis, reintroduzindo problemas de sincronização que o modelo de atores busca evitar.
4.3 Utilizar Mensagens Imutáveis
Como cada ator executa em uma única thread, não há preocupações com thread-safety dentro do método act. Porém, objetos enviados como mensagens são compartilhados entre atores, exigindo atenção.
A melhor abordagem é utilizar exclusivamente objetos imutáveis como mensagens. Classes case em Scala são ideais para isso, pois seus campos são vals por padrão. Utilize tipos básicos, String, Tuplas, List, Set e Map imutáveis.
Quando necessário enviar objetos mutáveis, envie uma cópia defensiva:
// Enviar cópia ao invés de referência direta
atorDestino ! arrayOriginal.clone()
// Ou converter para coleção imutável
atorDestino ! arrayOriginal.toList
4.4 Mensagens Autoexplicativas
Crie classes case específicas para cada tipo de mensagem ao invés de usar tuplas genéricas:
// Abordagem menos clara
consultorDNS ! ("www.exemplo.com", self)
// Abordagem mais expressiva
case class ConsultaDNS(hostname: String, solicitante: Actor)
consultorDNS ! ConsultaDNS("www.exemplo.com", self)
- Comunicação Entre Diferentes JVMs
Servidor:
import scala.actors.Actor
import scala.actors.remote.RemoteActor
import scala.actors.remote.Node
object ServidorRemoto {
def main(args: Array[String]): Unit = {
Actor.actor {
RemoteActor.alive(3000)
RemoteActor.register('servicoPrincipal, Actor.self)
Actor.loop {
Actor.receive {
case msg =>
println("Servidor recebeu: " + msg)
}
}
}
}
}
Cliente:
import scala.actors.Actor
import scala.actors.remote.RemoteActor
import scala.actors.remote.Node
object ClienteRemoto {
def main(args: Array[String]): Unit = {
Actor.actor {
val no = Node("127.0.0.1", 3000)
val atorRemoto = RemoteActor.select(no, 'servicoPrincipal)
println("-- Iniciando envio de mensagem")
atorRemoto ! "Mensagem do cliente"
println("-- Envio concluído")
}
}
}
Saída no servidor:
Servidor recebeu: Mensagem do cliente