Translate

sábado, 8 de enero de 2022

Concurrencia en Clojure


Clojure proporciona un estilo híbrido de programación funcional y estado mutable para resolver problemas de forma concurrente; el "Clojure Way" aprovecha las fortalezas de ambos para brindar un enfoque particularmente poderoso para la programación concurrente.

Si bien la programación funcional funciona increíblemente bien para algunos problemas, algunos tienen la modificación del estado como un elemento fundamental de la solución. Aunque puede ser posible crear una solución funcional a tales problemas, es más fácil pensar en ellos de una manera más tradicional. 

Un lenguaje funcional puro no proporciona soporte para datos mutables en absoluto. Clojure, por el contrario, es impuro: proporciona varios tipos diferentes de variables mutables conscientes de la concurrencia, cada una de las cuales es adecuada para diferentes casos de uso. Estos, en conjunto con las estructuras de datos persistentes de Clojure (vamos a cubriremos lo que significa persistente en este contexto más adelante) nos permiten evitar muchos de los problemas que tradicionalmente afligen a los programas concurrentes con estado mutable compartido.

La diferencia entre un lenguaje funcional impuro y un lenguaje imperativo es de énfasis. En un lenguaje imperativo, las variables son mutables por defecto y el código idiomático las modifica con frecuencia. En un lenguaje funcional impuro, las variables son inmutables por defecto y el código idiomático modifica aquellas que no lo son solo cuando es absolutamente necesario. Como veremos, las variables mutables de Clojure nos permiten manejar los efectos secundarios del mundo real sin dejar de ser seguros y consistentes.

Veremos cómo las variables mutables de Clojure funcionan en conjunto con las estructuras de datos persistentes para separar la identidad del estado. Esto permite que varios subprocesos accedan a variables mutables simultáneamente sin bloqueos (y el peligro asociado de interbloqueo) y sin ninguno de los problemas de estado mutable escapado u oculto. Comenzaremos por lo que podría decirse que es el más simple de los tipos de variables mutables de Clojure, el átomo.

Un átomo es una variable atómica  (los átomos de Clojure están construidos sobre java.util.concurrent.atomic). A continuación, se muestra un ejemplo de cómo crear y recuperar el valor de un átomo:

user=> (def my-atom (atom 42))

#'user/my-atom

user=> (deref my-atom)

42

user=> @my-atom

42

Un átomo se crea con átomo, que toma un valor inicial. Podemos encontrar el valor actual de un átomo con deref o @.
Si desea actualizar un átomo a un nuevo valor, se usa swap !:

user=> (swap! my-atom inc)
43
user=> @my-atom
43

Esto toma una función y le pasa el valor actual del átomo. El nuevo valor del átomo se convierte en el valor de retorno de la función. También podemos pasar argumentos adicionales a la función, como en este ejemplo:

user=> (swap! my-atom + 2)
45

El primer argumento pasado a la función será el valor actual del átomo, y luego cualquier argumento adicional dado para intercambiar. Entonces, en este caso, el nuevo valor se convierte en el resultado de (+ 43 2).

En raras ocasiones, es posible que desee establecer un átomo en un valor que no dependa de su valor actual, en cuyo caso puede usar reset !:

user=> (reset! my-atom 0)
0
user=> @my-atom
0

Los átomos pueden ser de cualquier tipo; muchas aplicaciones web utilizan un mapa atómico para almacenar datos de sesión, como en este ejemplo:

user=> (def session (atom {}))
#'user/session
user=> (swap! session assoc :username "paul")
{:username "paul"}
user=> (swap! session assoc :session-id 1234)
{:session-id 1234, :username "paul"}

Ya se que no vimos nada de concurrencia aun, pero paciencia, como me queda largo el post seguimos en otro...