2010-07-24 11 views
12

Il modello dell'attore è ben descritto da Gul Agha nel suo rapporto tecnico, "Attori: un modello di calcolo concorrente in sistemi distribuiti".In che modo "diventa" implementato nelle lingue che supportano il modello degli attori?

A pagina 49, spiega il "diventano" comando:

become <expression> 

Dopo aver chiamato "diventare X", un attore inoltrerà tutti i suoi messaggi alla cassetta postale di un altro attore (X).

Non sono sicuro, tuttavia, come questo sia implementato (è stato implementato affatto) in lingue come Erlang e Scala. È qualcosa che devo codificare manualmente? Che dire dell'efficienza? Agha mostra un'implementazione di uno stack usando il message-passing. Ogni volta che viene eseguito un pop o un push, a qualche attore viene aggiunto un altro link di inoltro ... Dopo centinaia di migliaia di operazioni, mi aspetto che una tale implementazione impieghi troppo tempo a inoltrare messaggi e non a fare un vero lavoro, a meno che alcuni le belle ottimizzazioni sono state eseguite sotto il cofano.

Quindi la mia domanda è: come viene implementato (o "diventa") implementato in tipici linguaggi di attori come Erlang, Scala (e librerie per altre lingue)?

risposta

3

Vedere a pagina 12 (pagina 26 della copia PDF che ho) del documento di Agha "Attori: un modello di calcolo concorrente nel sistema distribuito". "diventare" è il suo linguaggio attore è come si specifica # 2, il nuovo comportamento per l'attore. L'inoltro di messaggi a un altro attore è solo uno dei tanti possibili nuovi comportamenti.

Penso che con gli attori della Scala tu sia essenzialmente la stessa barca di Erlang se vuoi il comportamento di inoltro. Sotto il cofano, Scala "reagisce" e "reagisceWithin" funzionano molto come diventano, perché la funzione parziale definita dal blocco di reazione è il nuovo comportamento dell'attore, ma non sono sicuro che la somiglianza sia addirittura intenzionale.

La maggior parte (tutti?) Le implementazioni di "attori" si discostano abbastanza sostanzialmente dal modello dell'attore di Hewitt e dal linguaggio dell'attore di Agha. IIRC la parte della lingua che specifica il comportamento degli attori nella langauge di Agha non è nemmeno completa. La lingua nel suo complesso diventa Turing completa solo quando si considera una configurazione di attori. Direi che la relazione tra il modello dell'attore e gli attuali quadri degli attori è un po 'come la relazione tra l'orientamento degli oggetti in SmallTalk e l'orientamento agli oggetti in C++. C'è qualche concetto di trasferimento e termini simili, ma nei dettagli sono molto, molto diversi.

7

E non è implementata direttamente in Erlang, ma si potrebbe scrivere un become funzione banale che riceve un messaggio, lo inoltra a un altro processo e poi si definisce:

become(Pid) -> 
    receive 
     Msg -> Pid ! Msg 
    end, 
    become(Pid). 

(Una versione industriale-forza questo potrebbe aver bisogno di gestire segnali e altre stranezze, ma questa è l'essenza di questo.)

Chiamare become(Pid) trasformerebbe efficacemente il processo chiamante in processo Pid dal punto di vista del mondo esterno.

Questo non risolve i problemi evidenziati, con chiamate ripetute a become causando la crescita delle catene di inoltro. Tuttavia, questo in genere non si verifica in Erlang, e non sono sicuro di come i processi di Erlang siano mappati sul modello di attore.

4

L'attore è un cofuntore controvariante, quindi "diventare" è solo un comap.

In altre parole, un attore sui messaggi di tipo T è fondamentalmente una funzione di tipo (T => Unità).E questa è semplicemente la composizione della funzione (con la funzione di identità, forse).

E 'implementato in Scalaz:

val a1 = actor(a => println(a)) 
val a2 = a1.comap(f) 

L'attore a2 applica f per i suoi messaggi e poi invia il risultato al A1.

+0

La semantica della composizione delle funzioni sembra incoerente con le altre descrizioni di * diventa * in questa pagina, che ho letto come dicendo che la semantica di un attore sostituisce l'altra, non la aggiunge. –

+0

OK, quindi assegna il nuovo attore alla stessa variabile. – Apocalisp

4

Andando per Erlang qui.

A livello base, sono disponibili due opzioni. Se si sta solo che vogliono utilizzare become per modificare il comportamento di un determinato processo (vedi punto 2 della lista al punto 2.1.3), quindi è solo una questione di chiamare il ciclo successivo con una diversa funzione ricorsiva:

loop(State) -> 
    receive 
      {normal, Msg} -> loop(State); 
      {change, NewLoop} -> NewLoop(State) 
    end. 

Supponendo NewLoop è una funzione di ordine superiore, ogni volta che si invia il messaggio {change, NewLoop} a un processo in esecuzione inizialmente la funzione loop/1, sarà quindi utilizzare NewLoop come la sua definizione.

La seconda opzione è quella in cui si desidera che il processo funga da proxy (e modifica il comportamento). Questo è simile a quello suggerito da Marcelo Cantos. Basta avere il ciclo di processo e di inoltrare messaggi ad uno nuovo (rubare il suo codice):

become(Pid) -> 
    receive 
     Msg -> Pid ! Msg 
    end, 
    become(Pid). 

In teoria, questo fa quello che il giornale avrebbe chiesto. In pratica, però, ci sono dei rischi nell'usare la seconda opzione in un sistema Erlang reale. Nelle comunicazioni tra due processi, è un concetto frequente di "timbrare" il messaggio con l'identificatore del processo del mittente e che la risposta verrà contrassegnata con l'identificativo del processo del ricevente. Uno scambio di si potrebbero fare i seguenti messaggi (non si tratta di codice, solo una notazione a mano):

A = <0.42.0> <-- process identifier 
B = <0.54.0>, 
A: {A, "hello"}, 
B: {B, "hi"}, 
A: {A, "how are you?"}. 
B: {B, "Fine!"}. 

Così, quando A si aspetta un messaggio da B, sarà in grado di eguagliare solo per questi, utilizzando un modello ad esempio {B, Message}. Nel caso di un messaggio inoltrato, questo schema di indirizzamento diventa non valido e semplicemente rotto.

Un'alternativa sarebbe utilizzare i riferimenti (make_ref()) come schema di indirizzamento per abbinare i messaggi sopra i Pid di ritorno. Ciò separerebbe l'uso del Pid come indirizzo e identificatore usando due entità diverse.

C'è un altro problema, anche se l'indirizzamento è sicuro: dipendenze di processo. Cosa succede per i processi denominati, i processi che si bloccano, i monitor, ecc.? I processi client potrebbero avere monitor, collegamenti e quant'altro impostato per assicurarsi che nulla vada storto senza essere avvisati. Modificando il processo di routing per i segnali di uscita trappola e li trasmette, dovrebbe essere possibile per rendere le cose più sicuro:

loop(State) -> 
    receive 
      {normal, Msg} -> loop(State); 
      {become, Pid} -> 
       process_flag(trap_exit, true), % catch exit signals as messages 
       link(Pid),      % make it so if one process crashes, the other receives the signal 
       become(Pid) 
    end. 

become(Pid) -> 
    receive 
     {'EXIT', Pid, killed} -> exit(self(), kill); %% uncatchable termination 
     {'EXIT', Pid, normal} -> ok;     %% die normally too 
     {'EXIT', Pid, Reason} -> exit(Reason);  %% die for the same reason as Pid 
     Msg -> Pid ! Msg        %% forward the message 
    end, 
    become(Pid). 

Questa testato codice dovrebbe essere più sicuro in quanto i processi a seconda del primo processo otterrà gli stessi messaggi di errore come quello rappresentato da Pid in become(Pid), rendendo il routing piuttosto trasparente. Tuttavia, non darei alcuna garanzia che ciò possa funzionare a lungo termine con applicazioni reali.

Anche se è possibile e abbastanza concettualmente semplice da rappresentare e fare cose come become, la libreria standard di Erlang non è stata pensata con il secondo caso d'uso in mente.Per le applicazioni del mondo reale, posso solo raccomandare il primo metodo, che è ampiamente utilizzato da ogni applicazione Erlang che esiste al momento. La seconda è raro e potrebbe causare problemi


* * Le chiamate alla funzione become/1 nell'ultimo esempio dovrebbe probabilmente ?MODULE:become(Pid) per evitare potenziali incidenti relativi a codice carico caldo in futuro. *

3

Gli attori di Akka hanno un concetto "HotSwap" in cui è possibile inviare una nuova funzione parziale ad un attore che sostituisce il gestore di messaggi esistente. Il precedente è ricordato e può essere ripristinato. Cerca "hotswap" su http://doc.akkasource.org/actors per i dettagli.

+1

E anche, nella versione 0.10 non rilasciata, "diventa": http://github.com/jboner/akka/blob/master/akka-core/src/main/scala/actor/Actor.scala#L421 –

Problemi correlati