2009-08-09 17 views
110

Vorrei innanzitutto dire che ho un bel po 'di esperienza Java, ma solo di recente mi sono interessato ai linguaggi funzionali. Recentemente ho iniziato a guardare Scala, che sembra un linguaggio molto carino.Gli attori Scala: ricevere vs reagire

Tuttavia, ho letto su Scala's Actor framework in Programming in Scala e c'è una cosa che non capisco. Nel capitolo 30.4 si dice che l'utilizzo di react invece di receive rende possibile riutilizzare i thread, il che è positivo per le prestazioni, poiché i thread sono costosi nella JVM.

Ciò significa che, finché mi ricordo di chiamare react anziché receive, posso avviare il numero di attori che mi piace? Prima di scoprire Scala, ho suonato con Erlang e l'autore di Programming Erlang si vanta di generare oltre 200.000 processi senza perdere tempo. Odio farlo con i thread Java. Che tipo di limiti sto guardando in Scala rispetto a Erlang (e Java)?

Inoltre, come funziona il riutilizzo di questo thread in Scala? Supponiamo, per semplicità, di avere solo un thread. Tutti gli attori che avvierò verranno eseguiti sequenzialmente in questo thread o si verificherà una sorta di commutazione delle attività? Ad esempio, se avvii due attori con messaggi di ping-pong l'un l'altro, rischierò un deadlock se sono avviati nello stesso thread?

Secondo Programmazione in Scala, scrivendo soggetti a utilizzare react è più difficile che con receive. Sembra plausibile, dal momento che react non restituisce. Tuttavia, il libro continua a mostrare come è possibile inserire uno react all'interno di un ciclo utilizzando Actor.loop. Di conseguenza, si ottiene

loop { 
    react { 
     ... 
    } 
} 

che, a me, sembra abbastanza simile a

while (true) { 
    receive { 
     ... 
    } 
} 

che viene utilizzato in precedenza nel libro. Tuttavia, il libro dice che "in pratica, i programmi avranno bisogno di almeno alcuni receive". Quindi cosa mi manca qui? Cosa può receive fare che react non può, oltre a restituire? E perché me ne importa?

Infine, venendo al centro di ciò che non capisco: il libro continua a menzionare come l'utilizzo di react consente di scartare lo stack di chiamate per riutilizzare il thread. Come funziona? Perché è necessario scartare lo stack delle chiamate? E perché lo stack di chiamate può essere scartato quando una funzione termina lanciando un'eccezione (react), ma non quando termina restituendo (receive)?

Ho l'impressione che la programmazione in Scala abbia risolto alcuni dei problemi chiave qui, il che è un peccato, perché altrimenti è un libro davvero eccellente.

+2

vedere anche http://stackoverflow.com/questions/1526845/when-are-threads-created-for-scala-actors –

risposta

77

In primo luogo, ogni attore in attesa su receive occupa una discussione. Se non riceve mai nulla, quel thread non farà mai nulla. Un attore su react non occupa alcun thread finché non riceve qualcosa. Una volta che riceve qualcosa, un thread viene assegnato ad esso, e viene inizializzato in esso.

Ora, la parte di inizializzazione è importante. Ci si aspetta che un thread ricevente restituisca qualcosa, un thread che reagisce non lo è. Quindi lo stato precedente dello stack alla fine dell'ultimo react può essere ed è completamente scartato.Non è necessario salvare o ripristinare lo stato dello stack per rendere più veloce l'avvio del thread.

Ci sono vari motivi per cui si potrebbe desiderare l'uno o l'altro. Come sai, avere troppi thread in Java non è una buona idea. D'altra parte, poiché è necessario collegare un attore a un thread prima che sia possibile effettuare il numero react, è più veloce il receive di un messaggio rispetto a react. Quindi se hai attori che ricevono molti messaggi ma ne fanno pochissimo, il ritardo aggiuntivo di react potrebbe renderlo troppo lento per i tuoi scopi.

20

La risposta è "sì" - se i vostri attori non blocchino su nulla nel codice e si utilizza react, quindi è possibile eseguire il programma "concorrente" all'interno di un unico filo (provate ad impostare la proprietà di sistema actors.maxPoolSize per scoprirlo).

Una delle ragioni più ovvie per cui è necessario scartare stack di chiamate è che altrimenti il ​​metodo loop finirebbe in un StackOverflowError. Allo stato attuale, il framework termina in modo intelligente un numero react lanciando uno SuspendActorException, che viene catturato dal codice di loop che quindi esegue nuovamente il react tramite il metodo andThen.

Dai un'occhiata alla metodo mkBody in Actor e quindi il metodo seq per vedere come il ciclo in sé ripianifica - roba terribilmente intelligente!

19

Queste affermazioni di "scartare lo stack" mi hanno confuso anche per un po 'e penso di averlo capito ora e questa è la mia comprensione ora. In caso di "ricezione", esiste un blocco thread dedicato sul messaggio (utilizzando object.wait() su un monitor) e ciò significa che lo stack thread completo è disponibile e pronto per continuare dal punto di "attesa" al ricevimento di un Messaggio. Per esempio, se si ha avuto il seguente codice

def a = 10; 
    while (! done) { 
    receive { 
     case msg => println("MESSAGE RECEIVED: " + msg) 
    } 
    println("after receive and printing a " + a) 
    } 

il filo avrebbe aspettato nella chiamata ricevere fino a quando il messaggio viene ricevuto e poi avrebbe continuato su e stampare il "dopo di ricezione e stampa di un 10" messaggio e con il valore di "10" che si trova nel frame dello stack prima del thread bloccato.

In caso di reazione non esiste una tale filettatura dedicata, l'intero corpo del metodo del metodo di reazione viene catturato come chiusura e viene eseguito da qualche thread arbitrario sull'attore corrispondente che riceve un messaggio. Ciò significa che solo le istruzioni che possono essere catturate come una chiusura da sola verranno eseguite ed è qui che viene il tipo di ritorno di "Nothing". Si consideri il seguente codice

def a = 10; 
    while (! done) { 
    react { 
     case msg => println("MESSAGE RECEIVED: " + msg) 
    } 
    println("after react and printing a " + a) 
    } 

Se reagire aveva un tipo di ritorno di vuoto, vorrebbe dire che è legale per avere dichiarazioni dopo il "reagire" chiamata (nell'esempio la dichiarazione println che stampa il messaggio "dopo reagire e stampando un 10 "), ma in realtà non verrebbe mai eseguito poiché solo il corpo del metodo" reagisce "viene catturato e sequenziato per l'esecuzione successiva (all'arrivo di un messaggio). Dal momento che il contratto di reazione ha il tipo di ritorno di "Nulla" non possono esserci dichiarazioni che reagiscono, e lì non c'è motivo di mantenere lo stack. Nell'esempio sopra la variabile "a" non dovrebbe essere mantenuta poiché le istruzioni dopo che le chiamate di risposta non sono state eseguite affatto. Nota che tutte le variabili necessarie per il corpo di reagire sono già state catturate come chiusura, quindi possono essere eseguite correttamente.

Il framework di attori java Kilim esegue effettivamente la manutenzione dello stack salvando lo stack che viene srotolato nella risposta ricevendo un messaggio.

+0

Grazie, è stato molto istruttivo. Ma non intendevi '+ a' nei frammenti di codice, invece di' + 10'? – jqno

+0

Ottima risposta. Non ho neanche capito niente. – santiagobasulto

8

solo per averlo qui:

Event-Based Programming without Inversion of Control

Questi documenti sono collegati dalla scala api per attore e forniscono il quadro teorico per l'attuazione attore. Questo include perché la reazione potrebbe non tornare mai più.

+0

E il secondo foglio. Cattivo controllo spam ... :( [Attori che unificano discussioni ed eventi] [2] [2]: http://lamp.epfl.ch/~phaller/doc/haller07coord.pdf "Attori che unificano i thread ed eventi " – Hexren

0

Non ho fatto alcun lavoro importante con scala/akka, tuttavia capisco che c'è una differenza molto significativa nel modo in cui gli attori sono programmati. Akka è solo uno smart threadpool che è il tempo che taglia l'esecuzione degli attori ... Ogni fetta sarà un completamento di un messaggio a completamento di un attore diverso da Erlang che potrebbe essere per istruzione ?!

Questo mi porta a pensare che reagire sia meglio in quanto suggerisce al thread corrente di prendere in considerazione altri attori per pianificare dove ricevere "potrebbe" impegnare il thread corrente per continuare ad eseguire altri messaggi per lo stesso attore.

Problemi correlati