multithreading clojure future mutable

multithreading - Tipos de almacenamiento mutable Clojure



future (5)

Estoy intentando aprender Clojure a partir de la API y la documentación disponible en el sitio. No estoy muy claro acerca del almacenamiento mutable en Clojure y quiero asegurarme de que mi comprensión sea correcta. Por favor, hágame saber si hay alguna idea que me haya equivocado

Edit: estoy actualizando esto ya que recibo comentarios sobre su corrección.

Descargo de responsabilidad: Toda esta información es informal y potencialmente errónea. No uses esta publicación para entender cómo funciona Clojure.

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

Las referencias son ubicaciones compartidas entre hilos 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 la magia STM (rollbacks, 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 al enviar funciones de acción independientes para cambiar el estado del agente. Los agentes se devuelven de inmediato y, por lo tanto, no se bloquean, aunque el valor de un agente no se establece hasta que se completa una función enviada.

Los átomos son ubicaciones que pueden compartirse sincrónicamente entre hilos. Soportan la manipulación segura entre diferentes hilos.

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

  • Las variables son como variables antiguas regulares en lenguajes imperativos. (evitar cuando sea posible)
  • Los átomos son como Vars pero con la seguridad de compartir hilos que permite una lectura inmediata y un entorno seguro. (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 avisar a otros subprocesos que ha terminado de asignar.
  • Las referencias son ubicaciones compartidas que se bloquean 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 refs en esa transacción.

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


¡Suena como si realmente estuvieras recibiendo Clojure! buen trabajo :)

Vars tiene un "enlace de raíz" visible en todos los subprocesos y cada subproceso individual puede cambiar el valor que ve sin afectar a los otros subprocesos. Si mi entendimiento es correcto, una var no puede existir en un solo hilo sin un enlace de 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 encierra los cambios, pero solo cuando la transacción pudo finalizar en un estado coherente.


Creo que tu conclusión sobre Atoms es errónea:

Los átomos son como Vars pero con la seguridad de 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 una sola referencia:

  1. el valor antiguo se toma del átomo y se almacena en el subproceso local.
  2. La función se aplica al valor antiguo para generar un nuevo valor.
  3. si esto tiene éxito, se llama a comparar-y-establecer con valor antiguo y nuevo; solo si el valor del átomo no ha sido cambiado por ninguna otra hebra (aún es igual al valor anterior), el nuevo valor se escribe, de lo contrario, la operación se reinicia en (1) hasta que se realiza con el tiempo.

He encontrado dos problemas con su pregunta.

Tu dices:

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 de inmediato para ser leído por cualquier hilo.

Es decir, nunca tiene que esperar para obtener el valor de un agente (supongo que el valor cambiado por una acción es un proxy y un cambio atómico).

El código para el método deref de un Agent parece a esto (revisión SVN 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 de referencia) por

Las transacciones se comprometen en las llamadas a deref

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


Martin tiene razón cuando dice que la operación de los átomos se reinicia en 1. hasta que tenga éxito eventualmente. También se llama espín de espera. Si bien es una nota que realmente bloquea en un bloqueo, el hilo que realizó la operación se bloquea hasta que la operación sea exitosa, por lo que es una operación de bloqueo y no una operación asíncrona.

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 usar para entregar un valor de un hilo a otro. Hasta que se haya entregado el valor, cualquier intento de anular la referencia de la promesa se bloqueará.

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

Los futuros representan cálculos asíncronos. Son una forma de conseguir que el código se ejecute en otro subproceso y obtenga el resultado.

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


Vars no siempre tienen un enlace de 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]