Introduzione ai processi in Erlang

Come già scritto in questo post, a Giugno ho avuto la fortuna di assistere alla Erlang Factory di Londra. Il primo giorno, verso sera, durante un Lightning Talk, Joe Armstrong, il padre di Erlang, mentre si parlava di fault-tolerance e scalabilità ci interruppe; e con una semplicità disarmante ci spiegò che fault-tolerance e scalabilità in realtà sono la stessa identica cosa. Joe infatti dice che se due cose sono separate e non condividono nulla, esse possono scalare, perchè non rischiano di rubarsi le risorse a vicenda, e di riflesso, se una di queste entità (per momento astratte) fallisce, non intacca le altre, perchè ognuna è indipendente dall'altra. Ecco ottenuta la scalabilità e l'affidabilità. Due piccioni con una fava.

Ma vediamo come Erlang mette le basi per raggiungere tutto questo. Tutto parte dai processi (ecco che le entità diventano concrete), ovvero l'unità di base per creare un sistema Erlang.

I processi Erlang sono leggeri, ed appartengono al linguaggio di programmazione e non al Sistema Operativo, dimenticate quindi i pesanti Thread ed i costi dei creazione di ognuno di questi. In Erlang i processi sono quasi gratis, leggeri, con un foot-print piccolissimo, e soprattutto ognuno con la propria area di memoria ed il proprio garbage collector. Un sistema Erlang spesso e volentieri gestisce milioni di processi in contemporanea, quindi abituatevi ad avere a che fare con loro, perchè ci sarà da divertirsi, soprattutto quando grazie ad OTP andremo a coprire i Supervisors, ovvero il metodo che consente di sfruttare questa indipendenza tra processi per creare sistemi realmente stabili, che possono raggiungere livelli di affidabilità di nove noni, ovvero con il 99.9999999% di up-time (vedi lo Switch AXD301, un sistema composto da più di 1.5 milioni di righe di codice).

Per chiarire l'idea mi piace definire i processi Erlang come delle persone (non è farina del mio sacco, ma ancora del grandissimo Joe). Le persone non condividono la loro memoria, e comunicano tra loro scambiandosi dei messaggi. Ecco, immaginate ora che ogni persona viva in una stanza buia, e l'unico modo che abbiamo per inviargli un messaggio sia mettere nella sua cassetta delle lettere un messaggio. La persona legge un messaggio alla volta ed in base al tipo di messaggio esegue delle azioni ed eventualmente risponde con un altro messaggio. Ecco, ora al posto di queste strane persone che vivono in  una stanza buia, metteteci delle funzioni ed avete ottenuto i processi in Erlang. La seconda parte della spiegazione, quella delle persone sedute nella stanza buia è invece farina del sacco di Ferd nel suo fantastico Learn you Some Erlang for great good!

Nel seguito vedremo le basi dei processi in Erlang, essendo solo un'introduzione non approfondirò fino in fondo l'argomento ma tratterò solamente la creazione, l'invio di messaggi e la ricezione di questi. Per quanto riguarda registrazione, terminazione, linking e monitoring scriverò un altro post, altrimenti questo diventerebbe troppo lungo e poco digeribile.

Creazione di un processo

Per creare un processo, Erlang mette a disposizione diverse funzioni (tralasciamo per ora le funzioni spawn_link che come detto esulano dall'obbiettivo del post). Queste funzioni si trovano nel modulo erlang e sono esattamente 4. La più semplice, ovvero spawn/1 (l'1 indica l'arietà della funzione, in parole povere, il numero di parametri che essa richiede), la quale crea un processo che esegue la funzione passata come parametro; come ogni funzione di spawn, restituisce il Pid del processo.

Dicevo prima che queste funzioni di spawn restituiscono un Pid. Il Pid altro non è che un Identificatore di Processo, il quale ci permette di riferirci univocamente a quel determinato processo. Hanno la forma <A.B.C> dove A, B e C rappresentano rispettivamente (grazie a questa discussione su StackOverflow):

Per chiudere, le altre funzioni che ci permettono di creare un processo sono:

Invio di messaggi

Inviare messaggi ad un processo è estremamente semplice. La primitiva che ci permette di ottenere quello che vogliamo è un semplice punto esclamativo !, e l'istruzione ha la seguente forma: ! Messaggio, dove per Pid si intende l'identificatore del processo destinatario, mentre per messaggio si intende un qualunque termine Erlang (atom, tupla, integer, ecc…). Vedi l'esempio sottostante, dove nella Erlang Shell ho spedito 3 messaggi al processo contenuto nella variabile Mirko.

Ogni volta che inviamo un messaggio ad un processo, questo viene messo nella sua Inbox, dove attenderà in coda di essere processato.

Ricezione di messaggi

L'istruzione per ricevere i messaggi è semplicemente receive. Essa permette di gestire il flusso di esecuzione utilizzando il Pattern Matching. Vi ricordate il secondo punto del post specifico sul Pattern Matching?

La receive permette di analizzare i messaggi presenti nella Inbox del processo. Una volta che un messaggio corrisponde ad un Pattern di una clausola della receive, il codice di quella clausola viene eseguito ed il messaggio viene tolto dalla Inbox. In caso il messaggio non corrisponda a nessun Pattern, esso viene rimesso nella Inbox (non chiedetevi ora il perchè, questo è un concetto molto avanzato che richiede la conoscenza dell'hot code swapping).

Direi che per oggi è tutto, e possiamo chiudere questo post, che si è già dilungato abbastanza con un esempio semplicissimo per non confondere troppo le idee a chi è nuovo alla sistassi di Erlang ed ai linguaggi di programmazione funzionali in genere.

Esempio pratico

Nel codice che trovate proprio sotto al paragrafo, potete vedere un ri-arrangiamento della semplicissima funzione saluta che avevamo visto nel precedente post sul Pattern Matching. Essa è stata messa in questo caso in un modulo, argomento sicuro di un futuro post, quindi per il momento potete evitare di cercare di comprenderne la struttura se non vi è familiare.

Potete da subito notare l'utilizzo dell'istruzione receive la quale utilizza proprio il Pattern Matching per gestire il flusso di esecuzione. Notate anche che al termine di ogni clausola, richiamo ricorsivamente la funzione per tenere in vita il processo, che altrimenti terminerebbe per assenza di codice da eseguire. Inotre, in questo esempio stampo dei caratteri sullo Stream di I/O, a dire il vero questo sarebbe un side-effect, ma per il momento non mi interessa dato il livello accademico del post.

-module(persona).
-export([saluta/0]).

saluta() ->
     receive
    {uomo,_Nome, _Cognome} ->
        io:format("Salve Signore~n"),
        saluta();
    {donna, _Nome, _Cognome} ->
        io:format("Salve Signora~n"),
        saluta();
    _ ->
        io:format("Ciao~n"),
        saluta()
     end.

Procediamo ora alla creazione del processo nella Erlang Shell ed al seguente invio di messaggi al processo appena creato.

Introduzione ai Processi in Erlang Nota 1: io eseguo la Erlang Shell con “14 erl” solo perchè sul mio laptop ho installato varie versioni dell'Erlang Runtime System. Se volete approfondire l'argomento, Roberto Aloi scrisse un post illuminante tempo addietro.

Nota 2: alla riga 1, compilo il modulo persona.erl, ovvero quello che era riportato poche righe sopra. La tupla {ok, persona} indica che la compilazione è andata a buon fine e che è stato creato il file persona.beam. Si, per i coloro che vengono dal Java è come aver invocato javac (la primitiva c() non fa altro che chiamare infatti erlc.

Nota 3: alla riga 2 creo il processo e memorizzo il Pid nella variabile Mirko grazie ancora al Pattern Matching (punto 1 del post sul Pattern Matching) in modo da poter inviare i messaggi al processo appena creato.

Nota 4:Alle righe 3, 4 e 5 invio al processo dei messaggi che se controllate corrispondono a Pattern diversi nell'istruzione receive. Potete vedere come parti diverse di codice vengano eseguite. Dopo la stampa a video, viene anche stampato il messaggio inviato, ma solo perchè è il risultato dell'esecuzione dell'istruzione di invio del messaggio.

Bene, il post è lunghissimo e chiudo qua. Spero apprezziate, in caso di dubbi, commentate o contattatemi su Twitter. A presto :)


comments powered by Disqus