stringtranslate.com

Haskell concurrente

Haskell concurrente extiende [1] Haskell 98 con concurrencia explícita . Sus dos conceptos subyacentes principales son:

Sobre esto se construye una colección de abstracciones útiles de concurrencia y sincronización [2], como canales ilimitados , semáforos y variables de muestra.

Los subprocesos de Haskell tienen una sobrecarga muy baja: la creación, el cambio de contexto y la programación son todas tareas internas del entorno de ejecución de Haskell. Estos subprocesos de nivel Haskell se asignan a una cantidad configurable de subprocesos de nivel de SO, generalmente uno por núcleo de procesador .

Memoria transaccional de software

La extensión de memoria transaccional de software (STM) [3] de GHC reutiliza las primitivas de bifurcación de procesos de Concurrent Haskell. Sin embargo, STM:

Mónada STM

La mónada STM [4] es una implementación de la memoria transaccional de software en Haskell. Está implementada en GHC y permite modificar variables mutables en las transacciones .

Enfoque tradicional

Consideremos como ejemplo una aplicación bancaria y una transacción en ella: la función de transferencia, que toma dinero de una cuenta y lo coloca en otra. En la mónada IO, esto podría verse así:

tipo Cuenta = IORef Entero    transferencia :: Entero -> Cuenta -> Cuenta -> IO () transferencia monto de a = hacer fromVal <- readIORef desde -- (A) toVal <- readIORef a writeIORef desde ( fromVal - monto ) writeIORef a ( toVal + monto )                                 

Esto causa problemas en situaciones concurrentes donde pueden estar realizándose múltiples transferencias en la misma cuenta al mismo tiempo. Si hubiera dos transferencias transfiriendo dinero desde account fromy ambas llamadas a transfer ejecutaran line (A)antes de que cualquiera de ellas hubiera escrito sus nuevos valores, es posible que el dinero se ponga en las otras dos cuentas, y que solo uno de los montos que se transfieren se elimine de account from, creando así una condición de carrera . Esto dejaría a la aplicación bancaria en un estado inconsistente.

Una solución tradicional para este problema es el bloqueo. Por ejemplo, se pueden colocar bloqueos alrededor de las modificaciones de una cuenta para garantizar que los créditos y débitos se realicen de forma automática. En Haskell, el bloqueo se logra con MVars:

tipo Cuenta = MVar Entero    crédito :: Entero -> Cuenta -> IO () crédito monto cuenta = do actual <- takeMVar cuenta putMVar cuenta ( actual + monto )                    débito :: Entero -> Cuenta -> IO () débito importe cuenta = do actual <- takeMVar cuenta putMVar cuenta ( actual - importe )                    

El uso de estos procedimientos garantizará que nunca se pierda ni se gane dinero debido a una intercalación incorrecta de lecturas y escrituras en una cuenta individual. Sin embargo, si uno intenta combinarlas para crear un procedimiento como la transferencia:

transferencia :: Entero -> Cuenta -> Cuenta -> IO () importe de transferencia de a = debitar importe de abonar importe a                    

Todavía existe una condición de carrera: se puede debitar la primera cuenta y luego suspender la ejecución del hilo, lo que deja a las cuentas en su conjunto en un estado inconsistente. Por lo tanto, se deben agregar bloqueos adicionales para garantizar la corrección de las operaciones compuestas y, en el peor de los casos, es posible que sea necesario simplemente bloquear todas las cuentas independientemente de cuántas se utilicen en una operación determinada.

Transacciones atómicas

Para evitar esto, se puede utilizar la mónada STM, que permite escribir transacciones atómicas. Esto significa que todas las operaciones dentro de la transacción se completan completamente, sin que ningún otro hilo modifique las variables que utiliza nuestra transacción, o falla y el estado se revierte al estado en el que se encontraba antes de que se iniciara la transacción. En resumen, las transacciones atómicas o se completan completamente o es como si nunca se hubieran ejecutado. El código basado en bloqueos anterior se traduce de una manera relativamente sencilla:

tipo Cuenta = TVar Entero    crédito :: Entero -> Cuenta -> STM () crédito importe cuenta = do corriente <- readTVar cuenta writeTVar cuenta ( corriente + importe )                    débito :: Entero -> Cuenta -> STM () débito importe cuenta = do actual <- readTVar cuenta writeTVar cuenta ( actual - importe )                    transferencia :: Entero -> Cuenta -> Cuenta -> STM () importe de la transferencia de a = debitar importe de acreditar importe a                    

Los tipos de retorno de STM ()pueden utilizarse para indicar que estamos componiendo scripts para transacciones. Cuando llega el momento de ejecutar realmente una transacción de este tipo, atomically :: STM a -> IO ase utiliza una función. La implementación anterior se asegurará de que ninguna otra transacción interfiera con las variables que está utilizando (from y to) mientras se está ejecutando, lo que permite al desarrollador estar seguro de que no se encontrarán condiciones de carrera como la anterior. Se pueden realizar más mejoras para asegurarse de que se mantenga otra " lógica empresarial " en el sistema, es decir, que la transacción no intente sacar dinero de una cuenta hasta que haya suficiente dinero en ella:

transferencia :: Entero -> Cuenta -> Cuenta -> STM () transferir monto de a = hacer fromVal <- readTVar desde si ( fromVal - monto ) >= 0 entonces debitar monto de acreditar monto a de lo contrario reintentar                                  

Aquí se ha utilizado la retryfunción que revertirá una transacción y la intentará nuevamente. Reintentar en STM es inteligente porque no intentará ejecutar la transacción nuevamente hasta que una de las variables a las que hace referencia durante la transacción haya sido modificada por algún otro código transaccional. Esto hace que la mónada STM sea bastante eficiente.

Un programa de ejemplo que utiliza la función de transferencia podría verse así:

Módulo Principal donde  importar Control.Concurrent ( forkIO ) importar Control.Concurrent.STM importar Control.Monad ( forever ) importar System.Exit ( exitSuccess )       tipo Cuenta = TVar Entero    main = do bob <- newAccount 10000 jill <- newAccount 4000 repeatIO 2000 $ forkIO $ atómicamente $ transfer 1 bob jill forever $ do bobBalance <- atómicamente $ readTVar bob jillBalance <- atómicamente $ readTVar jill putStrLn ( "Saldo de Bob: " ++ show bobBalance ++ ", saldo de Jill: " ++ show jillBalance ) si bobBalance == 8000 entonces exitSuccess de lo contrario putStrLn "Intentando de nuevo."                                                       repeatIO :: Entero -> IO a -> IO a repeatIO 1 m = m repeatIO n m = m >> repeatIO ( n - 1 ) m                      newAccount :: Entero -> Cuenta IO monto de newAccount = monto de newTVarIO         transferencia :: Entero -> Cuenta -> Cuenta -> STM () transferir monto de a = hacer fromVal <- readTVar desde si ( fromVal - monto ) >= 0 entonces debitar monto de acreditar monto a de lo contrario reintentar                                  crédito :: Entero -> Cuenta -> STM () crédito importe cuenta = do corriente <- readTVar cuenta writeTVar cuenta ( corriente + importe )                    débito :: Entero -> Cuenta -> STM () débito importe cuenta = do actual <- readTVar cuenta writeTVar 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

  1. ^ Simon Peyton Jones, Andrew D. Gordon y Sigbjorn Finne. Concurrent Haskell. Simposio ACM SIGPLAN-SIGACT sobre principios de lenguajes de programación (PoPL). 1996. (Algunas secciones están desactualizadas con respecto a la implementación actual).
  2. ^ Las bibliotecas jerárquicas de Haskell, Control.Concurrent Archivado el 2 de agosto de 2012 en archive.today
  3. ^ Tim Harris, Simon Marlow, Simon Peyton Jones y Maurice Herlihy . Transacciones de memoria componibles. Simposio ACM sobre principios y práctica de programación paralela 2005 (PPoPP'05). 2005.
  4. ^ Control.Concurrente.STM