Prefazione (salta pure se vuoi, non è essenziale)
L’idea di questo post/traduzione mi è venuta leggendo un articolo di Brian Storti.
Di tanto in tanto cerco di (ri)avvicinarmi ad Elixir, per poterci mettere le mani in maniera sensata e tirare fuori un po’ di codice con cui divertirmi. Chi mi conosce bene tra l’altro sa che ho sempre avuto il pallino per le spiegazioni semplici. Sono convinto che l’apprendimento non debba essere una cosa noiosa, per quanto possibile, e che la capacità di sintesi è una cosa importante. Stasera, troppo stanco per scrivere del codice, mi sono detto: “ok, magari trovo qualche articolo interessante in giro“.
Mi sono messo così a pensare ai vari talk che ho sentito in giro ultimamente, alle discussioni che leggo sui vari forum/slack/chat e così via. Si parla spesso di Actor Model. Un concetto spesso descritto come “semplicissimo” (e lo è) ma raramente spiegato davvero. Non so dire con precisione perché, anche se una mezza idea ce l’ho: tendiamo a dare un sacco di cose per scontate, quando cerchiamo di spiegarne altre.
Ad ogni modo, del mio metodo di lavoro/studio/scrittura parlerò in un altro post. Torniamo all’articolo dell’ottimo Brian.
Ho trovato la domanda a cui rispondere: che cosa diamine è l’actor model? Il post che ho trovato è secondo su Google. La parte divertente arriva adesso però: cercando pagine in Italiano per “actor model”… non trovo nulla! Anzi, ad essere onesti ho trovato:
- un link ad un libro su Amazon (in inglese);
- un altro link ad un libro su Amazon (in inglese);
- un link ad un account instagram (non riguarda l’actor model, ovviamente, ma un tizio che fa l’attore ed il modello);
Insomma…
Cos’è l’Actor Model (ecco, leggi qui)
Partiamo da un principio semplicissimo. Fino a qualche anno fa le nostre CPU raddoppiavano di potenza senza problemi a ritmo quasi regolare. Adesso le cose sono cambiate e il miglioramento non segue lo stesso trend. Succede una cosa diversa: aumentano il numero dei core. Di conseguenza, per trarre il maggior vantaggio dai nostri software dobbiamo riuscire a scrivere del codice che può essere eseguito su più core. Concurrency, la chiamano.
Non è un problema nuovissimo, eh. Negli ultimi decenni sono stati fatti vari tentativi per “risolvere” il problema. Alcuni hanno funzionato di più, altri invece di meno (qualcuno ha detto thread?)
Ecco, l’actor model è un’altra di queste alternative che sembra funzionare alla grande.
In questo articolo vedremo cos’è senza, però, vedere del codice vero e proprio.
Gli Actor
Nell’actor model, l’actor è l’unità principale, l’elemento “atomico” per eccellenza. Un actor è un “qualcosa” che è capace di ricevere dei messaggi ed eseguire delle azioni di conseguenza. Ha un proprio stato (in poche parole, una o più variabili su cui lavora). Stop. A pensarci bene, il concetto non sembra diverso da quello di oggetto quando parliamo di programmazione ad oggetti, vero?
In realtà sono davvero due cose diverse: nell’actor model infatti due actor non potranno mai condividere la stessa memoria. Di conseguenza, un actor non potrà mai modificare direttamente lo stato di un altro actor. Dovrà sempre mandare un messaggio all’actor di cui vuole modificare lo stato e lasciar sbrigare il compito a lui.
Può sembrare astruso come concetto ma pensiamoci: se non viene mai condiviso uno stato (o della memoria), tutti quei problemi legati all’accesso e alla condivisione di quella memoria vengono meno! Di conseguenza, scrivere delle concurrency application diventa molto più semplice.
A questo punto, però, una domanda sorge spontanea…
Come comunicano due Actor?
Se un actor non può modificare direttamente lo stato di un altro, l’actor model ne prevede comunque la comunicazione attraverso il concetto di messaggio e mailbox. Nel model, ogni singolo actor ha un suo “indirizzo” specifico e può essere raggiunto da qualasiasi altro actor. Prima l’abbiamo detto: un actor può mandare e ricevere dei messaggi e agire di conseguenza.
Un po’ come un soldato, che aspetta delle istruzioni e quando queste arrivano può finalmente agire.
Ora, ogni actor può fare una sola cosa alla volta. Ed è proprio qui che viene introdotto il concetto di Mailbox. Anche qui niente di trascendentale: riprendendo l’esempio del soldato, basta pensare ad un insieme di ordini che arrivano in una “lista” e il soldato esegue questi ordini uno alla volta, man mano che arrivano, prendendoli dalla lista.
Ecco, la mailbox è quella lista. Ogni actor ha una sua mailbox. Fine!
Tre actor (e rispettive mailbox) che comunicano tra loro. Immagine presa da qui.
Una volta ricevuto un messaggio, un actor può fare tre cose:
- creare altri actor;
- mandare dei messaggi ad altri actor;
- decidere cosa fare con il prossimo messaggio;
I primi due punti sono comprensibilissimi. Riguardo il terzo, invece, le cose sono complesse ma solo apparentemente. Quando leggiamo “decidere cosa fare con il prossimo messaggio” in realtà stiamo dicendo una cosa leggermente diversa: decidere come un messaggio cambia lo stato interno dell’actor.
L’esempio riportato nell’articolo di Brian rende perfettamente l’idea. Immaginiamo di avere un actor calcolatrice che effettua operazioni sui numeri. Il suo stato iniziale sarà 0.
Se l’attore in questione riceve il messaggio add(1), il suo stato verrà mutato da 0 ad 1. Di conseguenza, stiamo anche decidendo che il prossimo messaggio cambierà lo stato da 1 a 2, e non più da 0 ad 1 come appena successo. Insomma, stiamo facendo una cosa molto semplice che di tanto in tanto viene spiegata in modo più “complesso”.
Let It Crash!
Un concetto molto importante introdotto, all’epoca, da Erlang è quello del “let it crash”. In poche parole, stop alla programmazione difensiva. L’importante non è più anticipare tutti i possibili problemi che possono verificarsi (in un certo senso, se vogliamo, è molto difficile se non impossibile) ma fare in modo che qualcuno si occupi di supervisionare questo codice.
WTF?
Ok, facciamo un passo indietro. In Erlang, il codice viene eseguito in processi. Potrebbe confonderti ma tieni a mente questo concetto: i processi Erlang sono degli attori.
L’esecuzione di questo codice è quindi parte di un attore, con le sue logiche specifiche.
Al posto di pensare ad ogni possibilità, il let it crash ci consiglia un approccio diverso: sticazzi se il processo va giù fin quando c’è un “supervisore” che lo ritira su.
La cosa divertente è che Erlang ed Elixir sono linguaggi pensati ad hoc per queste situazioni. Quindi tirare su un “supervisore” è semplicissimo (magari lo vedremo in futuro).
Grazie a questa tecnica è possibile creare dei sistemi che non hanno bisogno di procedure particolari in caso di crash, visto che c’è sempre un supervisor pronto a tirare su il processo che collassa.
Fine!
E niente, è tutto qui. Sono circa 1200 parole, ci avrai messo più o meno dieci minuti / un quarto d’ora a leggere tutto senza andare di fretta.
Sia chiaro: non sono concetti complessi. Se vieni da un altro modo di programmare le cose all’inizio saranno un po’ più “lente”. Se non hai problemi con l’inglese e vuoi spendere un’altra oretta su queste cose, ti consiglio questo video qui sotto.
Vale la pena anche solo per la maglietta/trip del tizio.
Per il resto, probabilmente in futuro scriverò ancora di functional e magari di Elixir. Penso mi ci divertirò non poco.