Translate

martes, 25 de enero de 2022

Concurrencia en Clojure - parte 4


Seguimos con concurrencia en Clojure

Las estructuras de datos persistentes son invaluables para la programación concurrente porque una vez que un subproceso tiene una referencia a una estructura de datos, no verá cambios realizados por ningún otro subproceso. Las estructuras de datos persistentes separan la identidad del estado. ¿Cuál es el nivel de combustible en su automóvil? En este momento, podría estar medio lleno. Algún tiempo después estará casi vacío, y unos minutos después de eso (después de que te detengas para llenar) estará lleno. La identidad "nivel de combustible en su automóvil" es una cosa, cuyo estado cambia constantemente. El “nivel de combustible en su automóvil” es en realidad una secuencia de diferentes valores: el 23/02/2012 a las 12:03 era 0,53; en 2012-02-23 14:30 era 0.12; y en 2012-02-23 14:31 era 1.00.

Una variable en un lenguaje imperativo completa (entrelaza, interconecta) la identidad y el estado: una única identidad solo puede tener un único valor, lo que hace que sea fácil perder de vista el hecho de que el estado es realmente una secuencia de valores a lo largo del tiempo. Las estructuras de datos persistentes separan la identidad del estado: si recuperamos el estado actual asociado con una identidad, ese estado es inmutable e muatble, sin importar lo que suceda con la identidad de la que recuperamos en el futuro.

Heráclito lo expresó así: No podías meterte dos veces en el mismo río; porque otras aguas están siempre fluyendo sobre ti. 

La mayoría de los lenguajes se aferran a la falacia de que el río es una sola entidad consistente; Clojure reconoce que está en constante cambio.

Debido a que Clojure es funcional, los átomos pueden no tener bloqueo; internamente, utilizan el método compareAndSet() en java.util.concurrent.AtomicReference. Eso significa que son muy rápidos y no se bloquean (por lo que no hay peligro de interbloqueo). Pero también significa ese intercambio necesita manejar el caso en el que el valor del átomo ha sido cambiado por otro hilo entre llamar a la función para generar un nuevo valor e intentar cambiar ese valor.

Si eso sucede, volverá a intentarlo. Descartará el valor devuelto por la función y lo llamará de nuevo con el nuevo valor del átomo. Ya vimos algo muy similar a esto cuando usamos ConcurrentHashMap. Esto significa que es esencial que la función pase a swap! no tiene efectos secundarios; si los tuviera, entonces esos efectos secundarios podrían ocurrir más de una vez. Afortunadamente, aquí es donde la naturaleza funcional de Clojure vale la pena: el código funcional naturalmente no tiene efectos secundarios.