Attori scala: ricevere vs reagire


110

Prima di tutto vorrei dire che ho molta esperienza con Java, ma solo di recente mi sono interessato ai linguaggi funzionali. Recentemente ho iniziato a guardare Scala, che sembra un linguaggio molto carino.

Tuttavia, ho letto del framework degli attori di Scala in Programming in Scala , e c'è una cosa che non capisco. Nel capitolo 30.4 si dice che usare reactinvece di receiverende possibile riutilizzare i thread, il che è positivo per le prestazioni, poiché i thread sono costosi nella JVM.

Questo significa che, purché mi ricordi di chiamare reactinvece di receive, posso avviare quanti attori voglio? Prima di scoprire Scala, ho suonato con Erlang, e l'autore di Programming Erlang si vanta di aver generato oltre 200.000 processi senza sudare. Non vorrei farlo con i thread Java. Che tipo di limiti sto osservando in Scala rispetto a Erlang (e Java)?

Inoltre, come funziona il riutilizzo di questo thread in Scala? Supponiamo, per semplicità, che io abbia un solo thread. Tutti gli attori che inizio correranno in sequenza in questo thread o si verificherà una sorta di cambio di attività? Ad esempio, se avvio due attori che si scambiano messaggi ping-pong, rischierò un deadlock se vengono avviati nello stesso thread?

Secondo Programming in Scala , scrivere attori da usare reactè più difficile che con receive. Questo sembra plausibile, poiché reactnon ritorna. Tuttavia, il libro continua a mostrare come si può inserire reactun loop usando Actor.loop. Di conseguenza, ottieni

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 programmi receive". Allora cosa mi manca qui? Cosa può receivefare che reactnon può, oltre a tornare? E perché mi interessa?

Infine, arrivando al nocciolo di ciò che non capisco: il libro continua a menzionare come using reactrenda possibile scartare lo stack di chiamate per riutilizzare il thread. Come funziona? Perché è necessario eliminare lo stack di 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 Programming in Scala abbia sorvolato su alcune delle questioni chiave qui, il che è un peccato, perché altrimenti è un libro davvero eccellente.


Risposte:


78

In primo luogo, ogni attore in attesa receiveoccupa un thread. Se non riceve mai nulla, quel thread non farà mai nulla. Un attore su reactnon occupa alcun thread finché non riceve qualcosa. Una volta ricevuto qualcosa, viene assegnato un thread e viene inizializzato in esso.

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

Ci sono vari motivi di prestazioni per cui potresti volerne uno o l'altro. Come sai, avere troppi thread in Java non è una buona idea. D'altra parte, poiché devi allegare un attore a un thread prima che possa reactfarlo, è più veloce a receiveun messaggio che reactad esso. Quindi, se hai attori che ricevono molti messaggi ma fanno molto poco con esso, il ritardo aggiuntivo di reactpotrebbe renderlo troppo lento per i tuoi scopi.


21

La risposta è "sì" - se i tuoi attori non stanno bloccando nulla nel tuo codice e tu stai usando react, allora puoi eseguire il tuo programma "concorrente" all'interno di un singolo thread (prova a impostare la proprietà di sistema actors.maxPoolSizeper scoprirlo).

Uno dei motivi più ovvi per cui è necessario scartare lo stack di chiamate è che altrimenti il loopmetodo finirebbe con un file StackOverflowError. Così com'è, il framework termina in modo piuttosto intelligente a reactlanciando a SuspendActorException, che viene catturato dal codice in loop che quindi lo esegue reactnuovamente tramite il andThenmetodo.

Dai un'occhiata al mkBodymetodo in Actore poi al seqmetodo per vedere come il ciclo si riprogramma da solo - roba terribilmente intelligente!


20

Quelle affermazioni di "scartare la pila" mi hanno confuso anche per un po 'e penso di aver capito ora e questa è la mia comprensione ora. In caso di "ricezione" c'è un blocco thread dedicato sul messaggio (utilizzando object.wait () su un monitor) e questo significa che lo stack di thread completo è disponibile e pronto per continuare dal punto di "attesa" alla ricezione di un Messaggio. Ad esempio, se avessi il codice seguente

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

il thread attenderà nella chiamata di ricezione fino a quando il messaggio non viene ricevuto e quindi continuerà e stamperà il messaggio "dopo aver ricevuto e stampato un 10" e con il valore "10" che si trova nello stack frame prima che il thread venga bloccato.

In caso di react non esiste un thread dedicato di questo tipo, l'intero corpo del metodo del metodo react viene catturato come chiusura e viene eseguito da un thread arbitrario sull'attore corrispondente che riceve un messaggio. Ciò significa che verranno eseguite solo quelle istruzioni che possono essere catturate come chiusura ed è qui che entra in gioco il tipo di ritorno "Nothing". Considera il codice seguente

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

Se reagire avesse un tipo di ritorno void, significherebbe che è legale avere dichiarazioni dopo la chiamata "reagire" (nell'esempio l'istruzione println che stampa il messaggio "dopo reagire e stampare un 10"), ma in realtà quello non verrebbe mai eseguito poiché solo il corpo del metodo "react" viene catturato e sequenziato per l'esecuzione successiva (all'arrivo di un messaggio). Poiché il contratto di reazione ha il tipo di ritorno "Niente", non possono esserci dichiarazioni che seguono a reagire, e non c'è motivo per mantenere lo stack. Nell'esempio sopra la variabile "a" non dovrebbe essere mantenuta poiché le istruzioni dopo le chiamate a react non vengono eseguite affatto. Si noti che tutte le variabili necessarie dal corpo di react sono già state catturate come chiusura, quindi può essere eseguito correttamente.

Il framework dell'attore java Kilim esegue effettivamente la manutenzione dello stack salvando lo stack che viene srotolato sulla reazione che riceve un messaggio.


Grazie, è stato molto istruttivo. Ma non intendevi +anegli snippet di codice, invece di +10?
jqno

Bella risposta. Non capisco neanche quello.
santiagobasulto

8

Solo per averlo qui:

Programmazione basata su eventi senza inversione del controllo

Questi documenti sono collegati dalla scala api per Actor e forniscono il quadro teorico per l'implementazione dell'attore. Questo include il motivo per cui reagire potrebbe non tornare mai più.


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

0

Non ho svolto alcun lavoro importante con scala / akka, tuttavia capisco che c'è una differenza molto significativa nel modo in cui gli attori sono programmati. Akka è solo un pool di thread intelligente che è l'esecuzione del time slicing degli attori ... Ogni time slice sarà l'esecuzione di un messaggio fino al completamento da parte di un attore a differenza di Erlang che potrebbe essere per istruzione ?!

Questo mi porta a pensare che reagire sia meglio in quanto suggerisce al thread corrente di considerare altri attori per la pianificazione in cui come ricevere "potrebbe" coinvolgere il thread corrente per continuare a eseguire altri messaggi per lo stesso attore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.