Concurrent Haskell (también Control.Concurrent o Concurrent and Parallel Haskell ) es una extensión del lenguaje de programación funcional Haskell , que agrega tipos de datos primitivos explícitos para la concurrencia . [ 1 ] Se agregó por primera vez a Haskell 98 y desde entonces se ha convertido en una biblioteca llamada incluida como parte del Glasgow Haskell Compiler .Control.Concurrent
Sus dos conceptos subyacentes principales son:
- Un tipo de datos primitivo que implementa un canal asíncrono
MVar αlimitado/de un solo lugar , que está vacío o contiene un valor de tipo .α - La capacidad de generar un hilo concurrente a través de la
forkIOprimitiva.
Sobre esto se construye un conjunto de abstracciones útiles de concurrencia y sincronización [ 2 ] como canales ilimitados , semáforos y variables de muestra .
Los hilos de Haskell tienen una sobrecarga muy baja: la creación, el cambio de contexto y la planificación son procesos internos del sistema de ejecución de Haskell . Estos hilos a nivel de Haskell se asignan a un número configurable de hilos a nivel del sistema operativo (SO), normalmente uno por núcleo del procesador .
memoria transaccional de software
La extensión de memoria transaccional de software (STM) [ 3 ] para Glasgow Haskell Compiler (GHC) reutiliza las primitivas de bifurcación de procesos de Concurrent Haskell. Sin embargo, STM:
- evita
MVaren favor deTVar. - Introduce las primitivas
retryy , lo que permite que se compongan acciones atómicas alternativas .orElse
mónada STM
La mónada STM [ 4 ] es una implementación de memoria transaccional de software en Haskell. Está implementada en GHC y permite que las variables mutables se modifiquen en transacciones .
Enfoque tradicional
Consideremos como ejemplo una aplicación bancaria y una transacción dentro de ella: la función de transferencia, que toma dinero de una cuenta y lo deposita en otra. En la mónada IO, esto podría verse así:
tipo Cuenta = IORef Enterotransferencia :: Entero -> Cuenta -> Cuenta -> IO () cantidad de transferencia de a = hacer fromVal <- readIORef de -- (A) toVal <- readIORef a writeIORef de ( fromVal - cantidad ) writeIORef a ( toVal + cantidad )Esto genera problemas en situaciones concurrentes donde se realizan múltiples transferencias en la misma cuenta simultáneamente . Si dos transferencias se realizan desde una cuenta fromy ambas se ejecutan (A)antes de que se escriban los nuevos valores, es posible que se deposite dinero en las otras dos cuentas, pero solo se retire una de ellas from, lo que crea una condición de carrera . Esto dejaría la aplicación bancaria en un estado inconsistente.
Una solución tradicional a este problema es el bloqueo. Por ejemplo, se pueden aplicar bloqueos a las modificaciones de una cuenta para garantizar que los créditos y débitos se produzcan de forma atómica. En Haskell, el bloqueo se realiza con MVars:
tipo Cuenta = MVar Enterocrédito :: Entero -> Cuenta -> IO () importe del crédito cuenta = hacer actual <- tomarMVar cuenta ponerMVar cuenta ( actual + importe )débito :: Entero -> Cuenta -> IO () importe de débito cuenta = hacer actual <- tomarMVar cuenta ponerMVar cuenta ( actual - importe )El uso de tales procedimientos garantizará que nunca se pierda ni se gane dinero debido a una intercalación inadecuada de lecturas y escrituras en ninguna cuenta individual. Sin embargo, si se intenta combinarlos para crear un procedimiento como una transferencia:
transferencia :: Entero -> Cuenta -> Cuenta -> IO () importe de transferencia de a = hacer importe de débito de importe de crédito aPersiste una condición de carrera: la primera cuenta puede ser debitada, la ejecución del hilo puede suspenderse, dejando a todas las cuentas en un estado inconsistente. Por lo tanto, es necesario agregar bloqueos adicionales para garantizar la corrección de las operaciones compuestas y, en el peor de los casos, podría ser necesario bloquear todas las cuentas, independientemente de cuántas se utilicen en una operación determinada.
Transacciones atómicas
Para evitar esto, se puede usar la mónada STM, que permite escribir transacciones atómicas. Esto significa que todas las operaciones dentro de la transacción se completan por completo, sin que ningún otro hilo modifique las variables que utiliza nuestra transacción, o bien falla y el estado se revierte al estado anterior a su inicio. En resumen, las transacciones atómicas se completan por completo o es como si nunca se hubieran ejecutado. El código basado en bloqueos anterior se traduce de forma relativamente sencilla:
tipo Cuenta = TVar Enterocrédito :: Entero -> Cuenta -> STM () importe del crédito cuenta = hacer actual <- leerTVar cuenta escribirTVar cuenta ( actual + importe )débito :: Entero -> Cuenta -> STM () importe de débito cuenta = hacer actual <- leerTVar cuenta escribirTVar cuenta ( actual - importe )transferencia :: Entero -> Cuenta -> Cuenta -> STM () importe de transferencia de a = hacer importe de débito de importe de crédito aLos tipos de retorno STM ()pueden interpretarse como una indicación de que estamos creando scripts para transacciones. Cuando llega el momento de ejecutar una transacción, se utiliza una función. La implementación anterior garantiza que ninguna otra transacción interfiera con las variables que utiliza (desde y hasta) durante su ejecución, lo que permite al desarrollador asegurarse de que no se produzcan condiciones de carrera como la descrita anteriormente. Se pueden realizar más mejoras para garantizar que se mantenga otra lógica de negocio en el sistema, por ejemplo, que la transacción no intente retirar dinero de una cuenta hasta que tenga fondos suficientes.atomically :: STM a -> IO a
transferencia :: Entero -> Cuenta -> Cuenta -> STM () importe de transferencia de a = hacer fromVal <- readTVar de si ( fromVal - importe ) >= 0 entonces hacer importe de débito de crédito importe a sino reintentarAquí se ha utilizado la retryfunción que revierte una transacción y la vuelve a ejecutar. El reintento en STM es inteligente, ya que no intenta ejecutar la transacción de nuevo hasta que alguna de las variables a las que hace referencia durante la transacción haya sido modificada por otro código transaccional. Esto hace que la mónada STM sea bastante eficiente.
Un ejemplo de programa que utilice la función de transferencia podría tener este aspecto:
módulo principal dondeimport Control.Concurrent ( forkIO ) import Control.Concurrent.STM import Control.Monad ( forever ) import System.Exit ( exitSuccess )tipo Cuenta = TVar Enteromain = do bob <- newAccount 10000 jill <- newAccount 4000 repeatIO 2000 $ forkIO $ atomically $ transfer 1 bob jill forever $ do bobBalance <- atomically $ readTVar bob jillBalance <- atomically $ readTVar jill putStrLn ( "Saldo de Bob: " ++ show bobBalance ++ ", Saldo de Jill: " ++ show jillBalance ) if bobBalance == 8000 then exitSuccess else putStrLn "Intentando de nuevo."repeatIO :: Entero -> IO a -> IO a repeatIO 1 m = m repeatIO n m = m >> repeatIO ( n - 1 ) mnewAccount :: Integer -> IO Account newAccount amount = newTVarIO amounttransferencia :: Entero -> Cuenta -> Cuenta -> STM () importe de transferencia de a = hacer fromVal <- readTVar de si ( fromVal - importe ) >= 0 entonces hacer importe de débito de crédito importe a sino reintentarcrédito :: Entero -> Cuenta -> STM () importe del crédito cuenta = hacer actual <- leerTVar cuenta escribirTVar cuenta ( actual + importe )débito :: Entero -> Cuenta -> STM () importe de débito cuenta = hacer actual <- leerTVar cuenta escribirTVar cuenta ( actual - importe )que debería imprimir "Saldo de Bob: 8000, saldo de Jill: 6000". Aquí atomicallyse ha utilizado la función para ejecutar acciones STM en la mónada IO.
Referencias
- ↑ Peyton Jones, Simon ; Gordon, Andrew D.; Finne, Sigbjorn (1 de enero de 1996). Escrito en Petersburg Beach, Florida. Haskell concurrente . Simposio ACM SIGPLAN-SIGACT sobre principios de lenguajes de programación (PoPL). Nueva York, Nueva York: Association for Computing Machinery (ACM). ISBN 978-0-89791-769-8.(Algunas secciones están desactualizadas con respecto a implementaciones posteriores).
- ↑ Las bibliotecas jerárquicas de Haskell , Control.Concurrent Archivado el 6 de noviembre de 2014 (error de fecha) en Wayback Machine
- ↑ Tim Harris, Simon Marlow , Simon Peyton Jones y Maurice Herlihy . Transacciones de memoria componibles . Simposio ACM sobre principios y práctica de la programación paralela 2005 (PPoPP'05). 2005.
- ↑ Control.Concurrent.STM
Enlaces externos
- Sitio web oficial
- Lenguajes de programación
- Lenguajes funcionales
- Lenguajes de programación de tipado estático
- Familia de lenguajes de programación Haskell
- Software libre programado en Haskell
- Software libre multiplataforma
- Compiladores libres y de código abierto
- Lenguajes de programación creados en 1996
- Software de 1996