Programmazione Concorrente e Distribuita — Prof. Alessandro Ricci, UniBO

Attori: modelli, framework ed Erlang

2026-04-27 170 min registrazione originale

In questa lezione

1. Perche il modello ad attori

Il modello ad attori nasce come alternativa al multi-threading tradizionale per la programmazione concorrente e distribuita. L'idea centrale e che tutto nel sistema sia un attore: un'entita autonoma, puramente reattiva, che comunica solo tramite scambio asincrono di messaggi.

Nella lezione, il Prof. Ricci sottolinea come il modello ad attori sia diventato sempre piu rilevante nel panorama mainstream. Framework come Akka (ora Pecco sulla JVM), Erlang, HTML5 Web Workers e Dart Isolates sono esempi concreti di adozione di questo modello. La ragione principale e che gli attori offrono una via d'uscita dai problemi classici della programmazione concorrente: race condition, deadlock, complessita della sincronizzazione manuale.

Idea chiave

Un attore e come un "oggetto concorrente": incapsula stato e flusso di controllo. A differenza degli oggetti classici dell'OOP, dove lo stato e protetto ma il metodo viene eseguito nel thread del chiamante, un attore possiede un proprio flusso di controllo logico e processa i messaggi uno alla volta.

Il Prof. Ricci ricorda come Alan Kay, padre della OOP, intendesse originariamente gli oggetti proprio come entita che comunicano tramite message passing. Il modello ad attori realizza compiutamente questa visione: ogni attore ha un identificatore unico, una mailbox (coda di messaggi) e un comportamento che determina come reagire ai messaggi ricevuti.

Thread classici

  • Memoria condivisa
  • Lock / synchronized
  • Race condition frequenti
  • Deadlock possibili
  • Debug complesso

Attori

  • Nessuno stato condiviso
  • Solo message passing
  • Race condition eliminate
  • Deadlock gestiti dal modello
  • Isolamento naturale

2. Il modello ad attori di Carl Hewitt

Introdotto da Carl Hewitt e colleghi al MIT negli anni '70 nel contesto dell'intelligenza artificiale, il modello ad attori e stato successivamente sviluppato da Gul Agha e Akinori Yonezawa negli anni '80 e '90. Il punto di partenza e la domanda: "come possiamo modellare il calcolo concorrente in modo matematico?"

La risposta e sorprendentemente semplice: l'attore e il primitivo universale del calcolo concorrente digitale. Tutto — incluse le strutture di controllo tradizionali (if, while, procedure) — puo essere modellato come pattern di scambio di messaggi tra attori.

  1. 1973 — Hewitt pubblica "A Universal Modular ACTOR Formalism"
  2. 1977 — "Viewing Control Structures as Patterns of Passing Messages", Journal of AI
  3. 1986 — Gul Agha formalizza il modello nella sua tesi "Actors: A model of concurrent computation in distributed systems"
  4. 1987 — Nasce Erlang in Ericsson per applicazioni telecom
  5. 1990 — Agha pubblica "Concurrent Object Oriented Programming" su CACM
  6. 2000+ — Akka (Scala/Java), Pecco (JVM), Web Workers, Dart Isolates

I tre pilastri del modello

Un attore e definito da tre elementi essenziali:

3. Primitive fondamentali: send, create, become

Il modello ad attori definisce solo tre primitive per comporre il comportamento di un attore. Ogni altra costruzione (sincronizzazione, ordinamento, pattern) e derivabile da queste tre.

Send — invia un messaggio asincrono a un attore specificato. E per la programmazione concorrente cio che l'invocazione di procedura e per la programmazione sequenziale: l'operazione fondamentale di interazione.

In Pecco si usa l'operatore tell o !: actorRef ! messaggio.

Create — crea un nuovo attore con un comportamento specificato. E per la programmazione concorrente cio che l'astrazione di procedura e per la programmazione sequenziale.

In Pecco: context.spawn(behavior, "nome").

Become — specifica un nuovo comportamento che l'attore usera per rispondere al prossimo messaggio. Questo da agli attori la capacita di avere un comportamento history-sensitive, necessario per modellare dati mutabili condivisi.

In Pecco: context.become(nuovoBehavior) restituito dalla receive.

Un aspetto cruciale e che un attore puo comunicare solo con attori di cui conosce l'identificatore. Questi identificatori possono essere scambiati all'interno dei messaggi, permettendo la costruzione di topologie di comunicazione dinamiche.

Nota del redattore

Le tre primitive sono state enunciate da Hewitt nel 1973 e rimangono valide oggi. Ogni framework moderno le implementa con nomi e sintassi diversi, ma la semantica e sempre quella.

4. Semantica macro-step e incapsulamento

Il modello ad attori introduce una semantica di esecuzione molto particolare: la semantica macro-step (run-to-completion). Quando un attore riceve un messaggio, l'intero handler viene eseguito completamente prima di passare al messaggio successivo. Questo elimina alla radice il problema delle race condition sullo stato interno.

flowchart LR
    A[Attore] --> B[mailbox: msg1, msg2, msg3]
    B --> C[estrai msg1]
    C --> D[esegui handler]
    D --> E[msg1 completato]
    E --> F[estrai msg2]
    F --> G[esegui handler]
    G --> H[msg2 completato]
    

Conseguenze della semantica macro-step

Attenzione

La semantica macro-step e una benedizione per la correttezza ma una maledizione per la leggibilita. Il Prof. Ricci cita il problema dello "asynchronous spaghetti" (Ricci & Santi, AGERE! 2012): la logica applicativa viene spezzata in una serie di handler disconnessi, ciascuno dei quali reagisce a un singolo messaggio.

Incapsulamento dello stato

Un attore non puo accedere direttamente allo stato interno di un altro attore. L'unico modo per interagire con un attore e inviargli un messaggio. Questo incapsulamento e molto piu forte di quello dell'OOP classico: non solo i dati, ma anche il flusso di controllo e incapsulato. L'attore decide quando e come processare un messaggio, e puo persino rimandare messaggi (stashing).

Location transparency

Per inviare un messaggio a un attore non serve sapere dove si trova: basta conoscere la sua identita (actor ref). Questo e il fondamento su cui si costruiscono i sistemi distribuiti basati su attori: lo stesso meccanismo funziona per attori locali e remoti.

5. Framework Pecco (ex Akka) sulla JVM

Nella parte centrale della lezione, il Prof. Ricci introduce Pecco, un framework ad attori per l'ecosistema JVM (Java/Scala). Pecco e il successore open-source di Akka: quando Lightbend (l'azienda dietro Akka) ha cambiato licenza in Business Source License, la comunita ha forkato il cuore tecnologico sotto il nome Apache Pekko, che il corso chiama Pecco.

Nota del redattore

Pecco e compatibile al 100% con Akka: gli stessi moduli, le stesse API, la stessa documentazione. Tutti gli esempi preesistenti per Akka funzionano con Pecco senza modifiche.

Le dipendenze necessarie per iniziare con Pecco includono il modulo core actor-type-typed e il modulo di logging basato su SLF4J. La versione typed dell'API e un'evoluzione importante: nell'API classica (untyped) si poteva mandare qualsiasi messaggio a qualsiasi attore, anche messaggi che l'attore non era in grado di gestire. Con l'API typed, ogni attore dichiara il tipo T dei messaggi che accetta, e il compilatore garantisce la correttezza a tempo di compilazione.

ConcettoRuolo
ActorSystemRoot del sistema ad attori; runtime che gestisce tutti gli attori
Behavior<T>Definisce il comportamento di un attore in risposta a messaggi di tipo T
ActorRef<T>Riferimento tipizzato a un attore; si usa per inviargli messaggi
ActorContext<T>Contesto dell'attore: permette di spawnare figli, fermarsi, accedere al sistema

6. Actor system, behavior e actor ref

Il sistema ad attori ha una struttura gerarchica. L'ActorSystem e la radice: crea gli attori di primo livello. Ogni attore puo a sua volta essere padre di altri attori, formando un albero di supervisione. Questo e ispirato al modello di Erlang, dove la filosofia e "Let It Crash": gli attori possono fallire, ma il padre (supervisore) decide come reagire.

flowchart TD
    S[ActorSystem] --> A1[Attore A]
    S --> A2[Attore B]
    A1 --> C1[Attore C - figlio di A]
    A1 --> C2[Attore D - figlio di A]
    A2 --> E1[Attore E - figlio di B]
    

Un behavior in Pecco e il comportamento dell'attore: una funzione che prende un messaggio e un contesto e restituisce il prossimo behavior. E importante capire che il prossimo behavior non deve essere per forza lo stesso: un attore puo cambiare comportamento dinamicamente in risposta ai messaggi.

Creazione di un attore in Pecco

import org.apache.pekko.actor.typed.*;
import org.apache.pekko.actor.typed.javadsl.*;

public class HelloWorld {
    public static Behavior<String> create() {
        return Behaviors.setup(context -> {
            return Behaviors.receiveMessage(message -> {
                System.out.println("Hello " + message);
                return Behaviors.same();
            });
        });
    }

    public static void main(String[] args) {
        ActorSystem<String> system = ActorSystem.create(HelloWorld.create(), "hello-system");
        system.tell("World!");
    }
}

L'actor ref e il riferimento tramite cui si interagisce con un attore. E parametrizzato sul tipo T dei messaggi accettati: ActorRef<T>. Se si prova a inviare un messaggio di tipo sbagliato, il programma non compila.

Per l'esame

Distinguere bene: Behavior<T> e cosa l'attore fa; ActorRef<T> e dove inviare messaggi; ActorContext<T> e il contesto per operazioni come spawn, stop, watch.

7. Esempio Ping-Pong in Pecco

Il Prof. Ricci mostra l'esempio classico per illustrare l'interazione tra attori: il protocollo Ping-Pong. Due attori si scambiano messaggi: Pinger e Ponger. Il messaggio Ping contiene un campo replyTo con l'actor ref a cui rispondere.

public interface PingProtocol { }
public static final class Ping implements PingProtocol {
    public final ActorRef<PongProtocol> replyTo;
    public Ping(ActorRef<PongProtocol> replyTo) {
        this.replyTo = replyTo;
    }
}
public interface PongProtocol { }
public static final class Pong implements PongProtocol { }

public static Behavior<PingProtocol> pingerBehavior() {
    return Behaviors.setup(context -> Behaviors.receiveMessage(msg -> {
        if (msg instanceof Ping p) {
            p.replyTo.tell(new Pong());
            return Behaviors.same();
        }
        return Behaviors.unhandled();
    }));
}

public static Behavior<PongProtocol> pongerBehavior() {
    return Behaviors.setup(context -> Behaviors.receiveMessage(msg -> {
        if (msg instanceof Pong) {
            System.out.println("Ricevuto Pong!");
            return Behaviors.same();
        }
        return Behaviors.unhandled();
    }));
}

Il pattern e chiaro: il messaggio Ping "incapsula" il protocollo di risposta portando con se il riferimento a chi deve rispondere. Questo evita la necessita di un meccanismo di sincronizzazione separato. Il Prof. Ricci sottolinea che questo e il modo idiomatico di fare request-response nel mondo ad attori: via messaggio con campo replyTo, non via chiamata bloccante.

Message-driven protocol

Invece di usare ask (che restituisce una Future), il modo piu idiomatico e puro e includere l'actor ref del mittente nel messaggio stesso. Questo mantiene il sistema completamente asincrono e reattivo.

8. Supervisione e fault tolerance

Uno degli aspetti piu potenti del modello ad attori e il meccanismo di supervisione, ispirato a Erlang. L'idea e che gli attori falliscano (lanciando eccezioni), ma il padre-supervisore decide come reagire. Non si cerca di prevenire tutti i fallimenti: si accetta che accadano e si fornisce un meccanismo di recovery ("Let It Crash").

Strategie di supervisione

Quando un attore figlio fallisce, il padre puo applicare diverse strategie:

StrategiaComportamento
RestartRiavvia l'attore figlio: viene creato da capo con stato iniziale (quello definito in setup)
ResumeRiprende l'esecuzione del figlio mantenendo lo stato interno corrente (delicato, perche lo stato potrebbe essere corrotto)
StopFerma definitivamente l'attore figlio
EscalatePropaga il fallimento al supervisore del padre, che decidera a sua volta

Il meccanismo di supervisione ha un effetto collaterale molto importante: la pulizia automatica delle risorse. Se un attore padre fallisce, tutti i figli vengono automaticamente fermati e le loro risorse rilasciate. Questo e molto diverso dal mondo dei thread, dove un thread orfano continua a vivere e il programmatore deve implementare meccanismi ad hoc per chiudere file, rilasciare lock, ecc.

Behaviors.setup(context -> {
    ActorRef<MyMsg> child = context.spawn(
        Behaviors.supervise(childBehavior())
            .onFailure(SupervisorStrategy.restart()),
        "child-actor"
    );
    return childBehavior();
});

Il Prof. Ricci menziona anche la funzione watch: un padre puo osservare un figlio e ricevere una notifica ChildFailed o Terminated quando termina. Questo permette al padre di reagire alla terminazione (es. cleanup aggiuntivo o terminazione a catena del sistema).

9. Pattern di messaggistica: fire-and-forget, ask, reply

Il modello ad attori prevede diversi pattern di interazione, ciascuno con i propri casi d'uso. Il Prof. Ricci li presenta in ordine crescente di complessita.

Fire-and-forget (tell)

L'attore invia un messaggio e non si aspetta una risposta. E il caso piu semplice e comune: si usa actorRef.tell(messaggio) o l'operatore !. Esempio: inviare un Increment a un contatore senza curarsi del risultato.

Request-response con replyTo

L'attore mittente include nel messaggio un campo replyTo: ActorRef<Risposta>. Il destinatario usa questo riferimento per inviare la risposta. E il pattern idiomatico del mondo ad attori: preserva l'asincronia e non richiede blocking.

Ask pattern

Pecco fornisce anche l'operatore ask che restituisce una Future. Questo e utile quando si vuole interagire con un attore da codice non-actor (es. da un main) o quando serve una risposta ma non si vuole implementare il protocollo manualmente.

import static org.apache.pekko.actor.typed.javadsl.AskPattern.ask;
import java.time.Duration;
import java.util.concurrent.CompletionStage;

CompletionStage<String> risposta = ask(
    actorRef,
    replyTo -> new RichiediDati(replyTo),
    Duration.ofSeconds(5),
    system.scheduler()
);

risposta.thenAccept(valore -> {
    System.out.println("Ricevuto: " + valore);
});

Il Prof. Ricci fa notare che ask e utile ma "esce un po' dal modello puro". L'uso della Future introduce un meccanismo che non e nativo del modello ad attori. Tuttavia, quando usato con giudizio, risolve il problema dell'asynchronous spaghetti: si possono combinare piu ask in sequenza con thenCompose o in parallelo con Promise.all.

10. Erlang: processo leggero e mailbox

Erlang e un linguaggio funzionale sviluppato in Ericsson a partire dal 1987 per costruire applicazioni telecom. Insieme ad Ada, e considerato uno dei linguaggi concorrenti piu robusti mai adottati dall'industria. Il cuore di Erlang e la BEAM (Bogdan/Bjorn's Erlang Abstract Machine), una macchina virtuale che implementa un modello ad attori nativo.

In Erlang, gli attori si chiamano processi (non processi OS, ma entita logiche leggerissime). La BEAM puo gestire centinaia di migliaia di processi su una singola macchina. La creazione di un processo costa pochi microsecondi.

flowchart LR
    subgraph BEAM[BEAM Virtual Machine]
        P1[Processo 1
mailbox] P2[Processo 2
mailbox] P3[Processo N
mailbox] end P1 -->|invia messaggio| P2 P2 -->|invia messaggio| P3 P3 -->|invia messaggio| P1

Processi vs Thread

CaratteristicaThread OSProcesso Erlang
Costo creazioneDecine di microsecondi~5 microsecondi
Numero massimoMigliaiaCentinaia di migliaia
Memoria per processoMB (stack + TCB)~300 parole
SchedulingPreemptivo (OS)Cooperativo (BEAM)
IsolamentoMemoria condivisaNessuna memoria condivisa
Attenzione

Il Prof. Ricci avverte: non confondete i processi Erlang con i processi del sistema operativo. I processi Erlang sono entita virtuali gestite dalla BEAM, molto piu leggeri e veloci da creare. Ogni processo ha una propria mailbox e non condivide memoria con altri processi.

11. Erlang: pattern matching e receive

Erlang e un linguaggio funzionale: un programma descrive una serie di funzioni, con pattern matching per determinare quale clausola eseguire. Le variabili iniziano con la lettera maiuscola (come in Prolog). Non esistono variabili globali.

La primitiva spawn lancia un nuovo processo e restituisce il PID (Process IDentifier). La send si fa con l'operatore !. La receive usa pattern matching con guardie per selezionare quale messaggio processare.

-module(area_server0).
-export([loop/0]).

loop() ->
    receive
        { rectangle, Width, Ht } ->
            io:format("Area of rectangle is ~p~n",[Width*Ht]),
            loop();
        { circle, R } ->
            io:format("Area of rectangle is ~p~n",[3.14159*R*R]),
            loop();
        Other ->
            io:format("I don't know what the area of a ~p is ~n",[Other]),
            loop()
    end.

Notare che: (1) ogni caso della receive termina con ; tranne l'ultimo che termina con .; (2) ogni caso chiama ricorsivamente loop() per rimettersi in ascolto del messaggio successivo; (3) il pattern Other cattura qualsiasi messaggio non riconosciuto.

Esempio Counter in Erlang

Il Prof. Ricci mostra come in Erlang anche un semplice contatore sia un processo. Non esiste memoria condivisa: tutto e un processo.

-module(counter).
-export([start/0]).

start() -> loop(0).

loop(Sum) ->
    receive
        {inc} ->
            loop(Sum + 1);
        {getValue, Pid} ->
            Pid ! {count_value, Sum},
            loop(Sum)
    end.

% 1> Pid = spawn(counter, start, []).
% 2> Pid ! {inc}.
% 3> Pid ! {getValue, self()}.

L'esempio della for comprehension in Erlang mostra come creare decine di migliaia di processi in pochi millisecondi, misurando il tempo di spawn:

-module(processes).
-export([max/1]).

max(N) ->
    Max = erlang:system_info(process_limit),
    io:format("Maximum allowed processes:~p~n",[Max]),
    statistics(runtime),
    statistics(wall_clock),
    L = for(1, N, fun() -> spawn(fun() -> wait() end) end),
    {_, Time1} = statistics(runtime),
    {_, Time2} = statistics(wall_clock),
    ...

% Output per N=20000:
% Process spawn time=5.5 (9.4) microseconds
"Everything is a process"

In Erlang, non esistono oggetti o dati mutabili condivisi. Anche un semplice contatore e un processo con la propria mailbox. Questa filosofia radicale elimina ogni possibilita di race condition.

12. Async programming: event loop e CPS

Il Prof. Ricci dedica una parte della lezione a inquadrare il modello ad attori nel contesto piu ampio della programmazione asincrona. L'event loop e il meccanismo fondamentale che sta sotto sia gli attori che la programmazione asincrona moderna.

Event loop

flowchart LR
    Q[Coda eventi] -->|estrai| EL[Event Loop]
    EL -->|dispatcher| H[Handler]
    H -->|completato| EL
    

Il pattern dell'event loop e semplice: un singolo thread attende eventi, li dispatca agli handler appropriati e ogni handler viene eseguito fino al completamento prima di servire l'evento successivo. E lo stesso pattern che sta sotto JavaScript, node.js, e — guarda caso — l'event loop incapsulato di ogni attore.

Nota del redattore

L'event loop e il pattern unificante tra programmazione ad attori, programmazione asincrona JavaScript, e architetture reattive. In tutti i casi, la regola e: non bloccare mai l'event loop.

Continuation-Passing Style (CPS)

Il CPS e uno stile di programmazione in cui il controllo viene passato esplicitamente sotto forma di continuazione (un callback). Invece di restituire un valore, una funzione CPS chiama la continuazione con il risultato. Questo e il fondamento della programmazione asincrona basata su callback.

function loadUserPic(userId, ret) {
    findUserById(userId, (user) => {
        loadPic(user.picId, ret);
    });
}

loadUserPic('john', (pic) => {
    ui.show(pic);
});
Callback hell

La nidificazione dei callback porta alla "piramide della dannazione" (pyramid of doom). Ogni callback nidificato aumenta l'indentazione e rende il codice difficile da leggere, capire e mantenere. Come citato dal Prof. Ricci: un commento da un developer di Node.js — "I love async, but I can't code like this".

13. Promise, async/await e coroutine

Per risolvere il callback hell, sono state introdotte le Promise. Una Promise e un oggetto che rappresenta il risultato futuro (o l'errore) di un'operazione asincrona. Puo trovarsi in tre stati: pending (creata, in attesa), resolved (completata con successo), rejected (completata con errore).

findUserById('john')
    .then(user => findPic(user.picId))
    .then(pic => ui.show(pic))
    .catch(err => console.error(err));

Promise.all([delayWithRand(1000), delayWithRand(2000)])
    .then(([r1, r2]) => console.log(r1, r2));

Async/await

L'evoluzione successiva e async/await, che permette di scrivere codice asincrono con uno stile sincrono. Una funzione async restituisce una Promise. L'operatore await sospende la funzione fino a quando la Promise non e risolta, senza bloccare il thread.

async function main() {
    console.log("Before");
    for (let i = 0; i < 3; i++) {
        await delay(1000);
        console.log("Step " + i);
    }
    console.log("After");
}

Coroutine

Il Prof. Ricci spiega che async/await e implementato usando le coroutine, una generalizzazione del concetto di subroutine che permette di sospendere e riprendere l'esecuzione. Le coroutine sono la base per implementare lightweight threads (fibers, virtual threads). Kotlin, Go (goroutine) e Java (virtual threads da JDK 19) usano questo meccanismo.

suspend fun greetAfter(name: String, delayMillis: Long) {
    delay(delayMillis)
    println("Hello, $name")
}

fun main() = runBlocking {
    val result = async {
        delay(1000)
        100
    }
    println("${result.await()}")
}
Per l'esame

Il Prof. Ricci fa notare che le coroutine non sono un mapping 1:1 con i thread: molte coroutine possono vivere su un singolo thread. Questo e il segreto dell'efficienza di Go (goroutine) e Kotlin (coroutine). La BEAM di Erlang funziona allo stesso modo: migliaia di processi logici su pochi thread OS.

14. Message delivery semantics e ordinamento

Un tema importante toccato nella lezione riguarda le garanzie di consegna dei messaggi tra attori. Pecco (come Akka e Erlang) adotta una semantica at-most-once: i messaggi possono essere persi (per crash, problemi di rete, ecc.), ma non saranno mai duplicati.

GaranziaDescrizione
At-most-onceOgni messaggio viene consegnato al massimo una volta. Possibile perdita, mai duplicazione.
At-least-onceOgni messaggio viene consegnato almeno una volta. Possibile duplicazione, mai perdita.
Exactly-onceOgni messaggio viene consegnato esattamente una volta. Massimo costo e complessita.

Ordinamento

Il modello ad attori garantisce un ordinamento sender-receiver: se A invia il messaggio M1 e poi M2 a B, M1 viene consegnato prima di M2. Tuttavia, non c'e garanzia di ordinamento tra coppie diverse: se A invia M1 a B e M2 a C, M2 potrebbe arrivare prima di M1.

Attenzione

Non date per scontato l'ordinamento globale dei messaggi. Il modello garantisce solo l'ordinamento per coppia sender-receiver. Se avete bisogno di ordinamento globale, dovete implementarlo voi usando pattern di messaggistica opportuni (es. sequencer actor).

15. Testing degli attori con Pecco

Pecco fornisce un modulo di testing integrato con ScalaTest e JUnit. Il test kit permette di creare un ActorSystem di test, spawnare attori e verificare che i messaggi vengano scambiati correttamente.

import org.apache.pekko.actor.testkit.typed.javadsl.*;

@Test
public void testPingPong() {
    var testKit = ActorTestKit.create();
    var probe = testKit.createTestProbe();
    var pinger = testKit.spawn(pingerBehavior());

    pinger.tell(new Ping(probe.ref()));
    probe.expectMessageClass(Pong.class);
}

Il Prof. Ricci sottolinea l'importanza di testare gli attori, soprattutto perche la semantica asincrona puo nascondere bug sottili. Il test kit di Pecco permette di: (1) spawnare attori in un ambiente controllato, (2) usare probe per catturare i messaggi in uscita, (3) impostare timeout per evitare test che si bloccano.

Nota del redattore

Il testing degli attori e piu vicino al testing di integrazione che al testing unitario. Anche un singolo attore ha bisogno del runtime (ActorSystem) per funzionare. Il test kit fornisce un ambiente leggero per questo scopo.

16. Errori comuni e best practice

Il Prof. Ricci conclude la lezione con una serie di raccomandazioni pratiche su cosa evitare quando si sviluppa con gli attori.

Per l'esame

Queste raccomandazioni sono state sottolineate esplicitamente dal professore durante la lezione. Ricordatele per il progetto e per l'esame.

Cosa evitare

Pratica scorrettaSpiegazione
Bloccare gli handlerNon fare Thread.sleep(), I/O bloccante, o attese in un handler. Blocca l'event loop dell'attore.
Usare ask dappertuttoPreferire messaggi con replyTo per mantenere la purezza del modello ad attori.
Trattare la supervisione come validazioneLa supervisione serve per gestire fallimenti imprevisti, non per validare input errati.
Esporre il contestoNon esporre mai ActorContext all'esterno dell'attore. Rompe l'incapsulamento.
Attori monoliticiMeglio tanti piccoli attori che uno grande. Si possono creare milioni di attori: sfruttate questa caratteristica.

Raccomandazioni positive

Il Prof. Ricci ricorda che il modello ad attori consente di definire in modo esplicito il protocollo di interazione tra componenti, offre un modo strutturato per gestire fallimenti e ciclo di vita, e costituisce la base per passare dalla programmazione concorrente a quella distribuita (argomento della prossima lezione).

Verifica le tue conoscenze

Quali sono le tre primitive fondamentali del modello ad attori secondo Hewitt?

Send (inviare un messaggio asincrono), Create (creare un nuovo attore con un comportamento specificato), Become (cambiare il comportamento dell'attore per il prossimo messaggio).

Cosa garantisce la semantica macro-step (run-to-completion)?

Quando un attore riceve un messaggio, l'intero handler viene eseguito completamente prima di passare al messaggio successivo. Questo elimina le race condition sullo stato interno dell'attore.

Che differenza c'e tra tell e ask in Pecco?

tell invia un messaggio fire-and-forget (non si aspetta risposta). ask invia un messaggio e restituisce una Future per attendere la risposta. Il pattern idiomatico nel mondo ad attori e usare tell con un campo replyTo nel messaggio.

Qual e la semantica di consegna dei messaggi di default in Pecco/Akka?

At-most-once: i messaggi possono essere persi ma mai duplicati. Per quanto riguarda l'ordinamento, viene garantito l'ordine per coppia sender-receiver (se A manda M1 e poi M2 a B, M1 arriva prima di M2).

Cosa significa "Let It Crash" nel contesto della supervisione?

E la filosofia di Erlang adottata anche da Pecco: non cercare di prevenire ogni possibile fallimento, ma accettarlo e fornire meccanismi di recovery automatici attraverso la gerarchia di supervisione. Il padre dell'attore fallito decide se riavviarlo, riprenderlo o fermarlo.

Qual e la differenza tra un processo Erlang e un thread OS?

Un processo Erlang e un'entita logica gestita dalla BEAM, non un thread OS. La creazione costa ~5 microsecondi (contro decine di microsecondi per un thread), si possono creare centinaia di migliaia di processi, e ogni processo ha una memoria di circa 300 parole (contro MB per un thread). Non condividono memoria.

Quali sono i tre stati di una Promise?

Pending (creata, in attesa di completamento), Resolved/Fulfilled (completata con successo), Rejected (completata con errore). Una volta risolta o rifiutata, la Promise e settled e il suo stato non cambia piu.

Quali sono gli errori piu comuni da evitare nella programmazione ad attori?

Bloccare gli handler (non fare mai blocking call), usare ask dappertutto (preferire messaggi con replyTo), usare la supervisione per validazione (serve per fallimenti imprevisti), esporre il contesto dell'attore all'esterno, creare attori monolitici (preferire tanti piccoli attori).