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

  1. Inicio
  2. Hacer copia privada de los datos compartidos
  3. Hacer actualizaciones en la copia privada
  4. Ahora:
    • Si datos compartidos no modificados → actualizar datos compartidos con copia privada y goto(Fin)
    • Si hay conflictos, descartar copia privada y goto(Inicio)
  5. 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étodo runInTransaction, 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.