Domanda

Sto tentando di imparare Clojure dall'API e dalla documentazione disponibile sul sito. Sono un po 'poco chiaro sulla memorizzazione mutevole in Clojure e voglio assicurarmi che la mia comprensione sia corretta. Per favore fatemi sapere se ci sono idee che ho sbagliato.

Modifica: sto aggiornando questo mentre ricevo commenti sulla sua correttezza.


Dichiarazione di non responsabilità: tutte queste informazioni sono informali e potenzialmente errate. Non utilizzare questo post per capire come funziona Clojure.


Vars contiene sempre un'associazione radice e possibilmente un'associazione per thread. Sono paragonabili alle variabili regolari nei linguaggi imperativi e non sono adatti per la condivisione di informazioni tra thread. (grazie Arthur Ulfeldt)

Ref sono posizioni condivise tra thread che supportano transazioni atomiche che possono modificare lo stato di qualsiasi numero di ref in una singola transazione. Le transazioni vengono impegnate all'uscita dalle espressioni di sincronizzazione (Dosync) e i conflitti vengono risolti automaticamente con la magia STM (rollback, code, attese, ecc.)

Agenti sono posizioni che consentono di condividere in modo asincrono le informazioni tra thread con un sovraccarico minimo inviando funzioni di azione indipendenti per modificare lo stato dell'agente. Gli agenti vengono restituiti immediatamente e pertanto non bloccano, sebbene il valore di un agente non sia impostato fino al completamento di una funzione di spedizione.

Atomi sono posizioni che possono essere condivise in modo sincrono tra thread. Supportano una manipolazione sicura tra thread diversi.

Ecco il mio riepilogo amichevole basato su quando usare queste strutture:

  • I Vars sono come vecchie variabili normali nelle lingue imperative. (evitare quando possibile)
  • Gli atomi sono come Vars, ma con sicurezza di thread-sharing che consente una lettura immediata e un'impostazione sicura. (grazie Martin)
  • Un agente è come un atomo ma invece di bloccarlo genera un nuovo thread per calcolarne il valore, si blocca solo se si sta modificando un valore e può far sapere agli altri thread che è stata assegnata.
  • I riferimenti sono posizioni condivise che si bloccano nelle transazioni. Invece di far decidere al programmatore cosa succede durante le condizioni di gara per ogni codice bloccato, iniziamo una transazione e lasciamo che Clojure gestisca tutte le condizioni di blocco tra i riferimenti in quella transazione.

Inoltre, un concetto correlato è la funzione future . A me sembra che un oggetto futuro possa essere descritto come un agente sincrono in cui non è possibile accedere al valore fino al completamento del calcolo. Può anche essere descritto come un atomo non bloccante. Queste sono concezioni accurate del futuro?

È stato utile?

Soluzione

Sembra che tu stia davvero ottenendo Clojure! buon lavoro :)

Vars ha un "quotazione radice" " visibile in tutti i thread e ogni singolo thread può modificare il valore visualizzato senza influire sugli altri thread. Se la mia comprensione è corretta, una var non può esistere in un solo thread senza un'associazione radice che è visibile a tutti e non può essere "rimbalzo". fino a quando non è stato definito con (def ...) la prima volta.

I ref sono impegnati alla fine della transazione (dosync ...) che racchiude le modifiche ma solo quando la transazione è stata in grado di terminare in uno stato coerente.

Altri suggerimenti

Penso che la tua conclusione sugli Atomi sia sbagliata:

  

Gli atomi sono come Vars ma con sicurezza di condivisione dei thread che si blocca fino a quando il valore non viene modificato

Gli atomi sono cambiati con scambia! o di basso livello con confronta e imposta! . Questo non blocca mai nulla. swap! funziona come una transazione con un solo riferimento:

  1. il vecchio valore viene preso dall'atomo e memorizzato thread-local
  2. la funzione viene applicata al vecchio valore per generare un nuovo valore
  3. se questo riesce, compare-and-set viene chiamato con valore vecchio e nuovo; solo se il valore dell'atomo non è stato modificato da nessun altro thread (è sempre uguale al vecchio valore), il nuovo valore viene scritto, altrimenti l'operazione si riavvia su (1) fino a quando alla fine non riesce.

Ho riscontrato due problemi con la tua domanda.

Dici:

  

Se si accede a un agente mentre si sta verificando un'azione, il valore non viene restituito fino al termine dell'azione

http://clojure.org/agents dice:

  

lo stato di un agente è sempre immediatamente disponibile per la lettura da qualsiasi thread

vale a dire. non devi mai aspettare per ottenere il valore di un agente (suppongo che il valore modificato da un'azione sia inviato in proxy e modificato atomicamente).

Il codice per il deref -metodo di un Agent è simile al seguente (revisione SVN 1382):

public Object deref() throws Exception{
    if(errors != null)
    {
        throw new Exception("Agent has errors", (Exception) RT.first(errors));
    }
return state;

}

Nessun blocco coinvolto.

Inoltre, non capisco cosa intendi (nella sezione Rif.) per

  

Le transazioni sono impegnate su chiamate a deref

Le transazioni vengono impegnate quando tutte le azioni del blocco Dosync sono state completate, non sono state generate eccezioni e nulla ha causato il tentativo di ripetere la transazione. Penso che deref non abbia nulla a che fare con questo, ma forse ho frainteso il tuo punto.

Martin ha ragione quando dice che l'operazione Atomi riparte da 1. fino a quando alla fine non avrà successo. Si chiama anche spin wait. Mentre si nota che sta davvero bloccando un blocco, il thread che ha eseguito l'operazione viene bloccato fino a quando l'operazione non riesce, quindi si tratta di un'operazione di blocco e non di un'operazione asincrona.

Anche sui futures, Clojure 1.1 ha aggiunto astrazioni per promesse e futures. Una promessa è un costrutto di sincronizzazione che può essere utilizzato per fornire un valore da un thread a un altro. Fino a quando il valore non sarà stato consegnato, qualsiasi tentativo di dereferenziare la promessa verrà bloccato.

(def a-promise (promise))
(deliver a-promise :fred)

I futures rappresentano calcoli asincroni. Sono un modo per ottenere il codice da eseguire in un altro thread e ottenere il risultato.

(def f (future (some-sexp)))
(deref f) ; blocks the thread that derefs f until value is available

I Vars non hanno sempre un'associazione radice. È legale creare un var senza associazione usando

(def x)

o

(declare x)

Il tentativo di valutare x prima che abbia un valore comporterà

Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top