Pregunta

Estoy intentando aprender Clojure de la API y la documentación disponible en el sitio. No estoy muy claro sobre el almacenamiento mutable en Clojure y quiero asegurarme de que mi comprensión sea correcta. Avíseme si hay alguna idea que me haya equivocado.

Editar: estoy actualizando esto a medida que recibo comentarios sobre su corrección.


Descargo de responsabilidad: Toda esta información es informal y potencialmente incorrecta. No use esta publicación para comprender cómo funciona Clojure.


Vars siempre contienen un enlace raíz y posiblemente un enlace por subproceso. Son comparables a las variables regulares en lenguajes imperativos y no son adecuadas para compartir información entre hilos. (gracias Arthur Ulfeldt)

Refs son ubicaciones compartidas entre subprocesos que admiten transacciones atómicas que pueden cambiar el estado de cualquier número de referencias en una sola transacción. Las transacciones se confirman al salir de las expresiones de sincronización (dosync) y los conflictos se resuelven automáticamente con magia STM (retrocesos, colas, esperas, etc.)

Los

Agentes son ubicaciones que permiten que la información se comparta de forma asíncrona entre subprocesos con una sobrecarga mínima mediante el envío de funciones de acción independientes para cambiar el estado del agente. Los agentes se devuelven inmediatamente y, por lo tanto, no se bloquean, aunque el valor de un agente no se establece hasta que se completa una función despachada.

Átomos son ubicaciones que pueden compartirse sincrónicamente entre hilos. Admiten una manipulación segura entre diferentes hilos.

Aquí está mi resumen amigable basado en cuándo usar estas estructuras:

  • Los Vars son como variables antiguas regulares en lenguajes imperativos. (evitar cuando sea posible)
  • Los átomos son como Vars pero con seguridad para compartir hilos que permite una lectura inmediata y una configuración segura. (gracias Martin)
  • Un agente es como un átomo, pero en lugar de bloquearlo genera un nuevo subproceso para calcular su valor, solo bloquea si está en medio de cambiar un valor, y puede informar a otros subprocesos que ha terminado de asignar.
  • Las referencias son ubicaciones compartidas que se encierran en transacciones. En lugar de hacer que el programador decida qué sucede durante las condiciones de carrera para cada pieza de código bloqueado, simplemente iniciamos una transacción y dejamos que Clojure maneje todas las condiciones de bloqueo entre los árbitros en esa transacción.

Además, un concepto relacionado es la función future . Para mí, parece que un futuro objeto puede describirse como un Agente síncrono donde no se puede acceder al valor hasta que se complete el cálculo. También se puede describir como un átomo sin bloqueo. ¿Son estas concepciones precisas del futuro?

¿Fue útil?

Solución

¡Parece que realmente estás recibiendo Clojure! buen trabajo :)

Los Vars tienen un enlace raíz '' visible en todos los hilos y cada hilo individual puede cambiar el valor que ve sin afectar a los otros hilos. Si mi comprensión es correcta, no puede existir una var en un solo hilo sin un enlace raíz que sea visible para todos y no puede ser "rebote". hasta que se haya definido con (def ...) la primera vez.

Las referencias se confirman al final de la transacción (dosync ...) que incluye los cambios, pero solo cuando la transacción pudo finalizar en un estado coherente.

Otros consejos

Creo que su conclusión sobre los átomos es incorrecta:

  

Los átomos son como Vars pero con seguridad para compartir hilos que bloquea hasta que el valor ha cambiado

Los átomos se cambian con swap! o de bajo nivel con compare-and-set! . Esto nunca bloquea nada. swap! funciona como una transacción con solo una referencia:

  1. el valor anterior se toma del átomo y se almacena localmente en el hilo
  2. la función se aplica al valor anterior para generar un nuevo valor
  3. si esto tiene éxito, se compara comparar y establecer con el valor antiguo y el nuevo; solo si el valor del átomo no ha sido modificado por ningún otro hilo (igual al valor anterior), se escribe el nuevo valor; de lo contrario, la operación se reinicia en (1) hasta que finalmente tenga éxito.

He encontrado dos problemas con su pregunta.

Usted dice:

  

Si se accede a un agente mientras se produce una acción, el valor no se devuelve hasta que la acción haya finalizado

http://clojure.org/agents dice:

  

el estado de un Agente siempre está disponible inmediatamente para que lo lea cualquier hilo

I.e. nunca tiene que esperar para obtener el valor de un agente (supongo que el valor modificado por una acción se representa por proxy y se modifica atómicamente).

El código para el método deref de un Agent tiene este aspecto (SVN revisión 1382):

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

}

No hay bloqueo involucrado.

Además, no entiendo lo que quieres decir (en tu sección Ref) por

  

Las transacciones se confirman en llamadas a deref

Las transacciones se confirman cuando se han completado todas las acciones del bloque de sincronización, no se han lanzado excepciones y nada ha provocado el reintento de la transacción. Creo que deref no tiene nada que ver con eso, pero tal vez no entiendo tu punto.

Martin tiene razón cuando dice que la operación Atoms se reinicia en 1. hasta que finalmente tenga éxito. También se llama giro en espera. Si bien se nota que realmente se bloquea en un bloqueo, el subproceso que realizó la operación se bloquea hasta que la operación tiene éxito, por lo que es una operación de bloqueo y no una operación asincrónica.

También sobre futuros, Clojure 1.1 ha agregado abstracciones para promesas y futuros. Una promesa es una construcción de sincronización que se puede utilizar para entregar un valor de un hilo a otro. Hasta que se haya entregado el valor, cualquier intento de desreferenciar la promesa se bloqueará.

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

Los futuros representan cálculos asincrónicos. Son una forma de hacer que el código se ejecute en otro hilo y obtener el resultado.

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

Los Vars no siempre tienen un enlace raíz. Es legal crear una var sin un enlace usando

(def x)

o

(declare x)

Intentar evaluar x antes de que tenga un valor dará como resultado

Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top