Memoria Transaccional Software con Clojure/Java
STM
Técnica de control de la concurrencia alternativa al modelo de bloqueos, similar al sistema de transacciones de una BD. Las operaciones sobre datos compartidos se ejecutan dentro de una transacción, lo cuál permite:
- Disponer de operaciones libres de bloqueos (e interbloqueos)
- Aumentar el paralelismo en el acceso a los datos compartidos
- Evitar las condiciones de concurso continuas sobre un mismo cerrojo
La STM simplifica la p.concurrente sustituyendo el acceso a datos compartidos bajo control de cerrojo, por el acceso a los mismos dentro de una transacción.
Concepto de transacción
Una transacción es una secuencia de operaciones desarrolladas por una tarea concurrente (proceso o hebra) sobre un conjunto de datos compartidos que puede terminar de dos maneras:
- Validando (commit) las transacciones, todas las actualizaciones efectuadas por la tarea sobre los datos compartidos tienen efecto atómico
- Abortando la transacción, los cambios no tienen efecto y los datos no cambian. Típicamente la transacción se intenta de nuevo (roll-back)
Las transacciones habitualmente cumplen las conocidas propiedades ACID:
- Atomicidad
- Consistencia
- Aislamiento
- Durabilidad
Desde un punto de vista técnico
El marco que da soporte a la STM no es técnicamente sencillo, ya que
- Debe garantizar la ejecución atómica de las transacciones
- Asegura que se cumple ACID y todo ello sin usar cerrojos
Se propone enfoques basados en hardware, en software e híbridos para soportar técnicamente el econcepto de memoria transaccional software, para Java, hay decenas de implementaciones posibles.
Todo lenguaje o API que soporte STM necesita un manejador de transacciones, que es el algoritmo que se encarga de procesor las transacciones que el programador escribe garantizando las propiedades ACID.
Algoritmo de procesamiento de transacción
Algoritmo de procesamiento de una Transaccion
- Inicio
- Hacer copia privada de los datos compartidos
- Hacer actualizaciones en la copia privada
- Ahora:
- Si datos compartidos no modificados → actualizar datos compartidos con copia privada y goto(Fin)
- Si hay conflictos, descartar copia privada y goto(Inicio)
- Fin
STM con el lenguaje Clojure
Con clojure, los valores, estados, son inmutables y las identidades únicamente pueden cambiar dentro de una transacción
ref
permite crear identidades mutables
def
no es únicamente válido para funciones, sino que es aplicable a cualquier tipo. Nosotros la utilizarremos junto a ref
Transacciones con Clojure
Una transacción se crea envolviendo el código de interés (que normalmente serán los datos compartidos) en un bloque dosync de forma parecida a la que hacíamos con synchrinized
en Java.
OJO, no es lo mismo
Dentro del bloque podemos cambiar el estado de la entidad:
- Utilizando
ref-set
que establece el nuevo estado de la identidad y lo devuelve - Utilizando
alter
que establece el nuevo estado de la identidad como el resultado de aplicar una función y lo devuelve
(def balance (ref 0))
(println "El saldo es " @balance)
(dosync( ref-set balance 100 )) ; sin dosync no funciona
(println "El saldo es ahora " @balance)
Clojure en Java
Clojure es interpretado por la JVM, por lo que existe cierto grado de compatibilidad entre Java y Clojure. Si utilizamos la API de Clojure, en Java podemos tener disponibles dos nuevas clases:
- La clase
Ref
que sirve para que una identidad se asocia a un estado (valor) en el ambiente de Clojure. - La clase
LockingTransaction
que proporciona, entre otros, el métodorunInTransaction
, cuyo parámetro es un objeto que implementa la interfaz Callable (es decir, una tarea concurrente), y que la ejecuta dentro de una transacción, gracias al manejador de transacciones de Clojure.