You are on page 1of 20

Concurrencia con Haskell

Antonio Francisco Burdallo Berrocal
1

Índice
Introduccion Ideas Basicas Procesos: forkIO() Sincronizacion y Comunicación Mvars Canales y Mensajes Planificacion Implementacion Recolector de basura
2

La solución a todo esto se encuentra en la utilización de monadas 3 . Con ello se trata de dar mayor expresividad a los programas que hacen uso de un sistema de entrada salida muy sofisticado. En los lenguajes imperativos esto funciona sin problemas. Concurrent Haskell es una extensión concurrente del lenguaje Haskell convencional. ya que estos solo se van a producir cuando la función se evalúe y no sabemos cuando va a suceder esto. por lo que la concurrencia esta a la orden del día. para conseguir el efecto deseado. imprimir un carácter por pantalla no es mas q el resultado de llamar a una función que tiene como efecto lateral la impresión de dicho carácter por pantalla. por ejemplo. en las llamadas a funciones. La mayoría de los lenguajes hace uso de los efectos laterales.Introducción Los sistemas informáticos de hoy día hacen un uso muy exhaustivo de los recursos. pero cuando nos encontramos con la evaluación perezosa si que tenemos un problema con los efectos laterales.

que permita la comunicación y la cooperación de los procesos 4 .Ideas Básicas Las ideas básicas de la concurrencia en haskell son 2: Procesos y los mecanismos necesarios para lanzarlos ForkIO () Cambio automático de contexto.

Al Evaluar esta expresión se crea una nueva hebra que se ejecuta concurrentemente con el proceso padre. Las hebras pueden ser dormidas utilizando threadDelay – ThreadDelay :: Int -> IO () 5 . una acción y genera un proceso que representa dicha acción. La llamada a forkIO devuelve el identificador del proceso creado como resultado.Procesos: ForkIO () Para crear nuevas hebras en Haskell hacemos uso de la primitiva forkIO – ForkIO :: IO a -> IO ThreadId ForkIO toma un argumento como parámetro.

ForkIO es asimétrico. ambos pueden modificar el estado del sistema. Desde el instante en que el proceso padre genera un proceso hijo. El nuevo proceso no tiene nombre. la ejecución de esta primitiva en el proceso padre genera un proceso hijo que se ejecuta concurrentemente con la continuación de la ejecución del proceso padre. introduciendo indeterminismo..Características ForkIO () Debido a que la implementación de ForkIO hace uso de la evaluación perezosa de haskell. en cuyo caso el primero debería bloquearse y esperar a que el segundo termine. ya que un proceso podría intentar evaluar una computación aplazada (thunk) que ya esta siendo evaluada por otro proceso. De modo que no podemos hacer que ninguna operación espere a su finalización o “matar” al proceso generado 6 . es necesaria la sincronización entre procesos.

MVar 7 . como un fichero. por lo que se hace necesario introducir los semáforos – Necesitamos de otras primitivas que permitan operar con mas de un proceso de forma in determinista – Es necesario establecer una forma conveniente de comunicación entre procesos Para dar solución a todo lo anterior se introduce un nuevo tipo.Sincronización y Comunicación En principio es fácil pensar que es suficiente la utilización de forkIO para crear programas concurrentes con Haskell Podemos utilizar la evaluación perezosa de listas para la comunicación entre 2 procesos Pero veamos ahora algunas razones por las que debemos introducir nuevos mecanismos para la comunicación y sincronización entre procesos: – Los procesos pueden necesitar de la exclusión mutua a la hora de acceder a un determinado recurso.

para cualquier tipo t es el nombre de una “mutable location” (variable) que o bien puede estar vacía o contiene un valor de tipo t.MVar El valor del tipo MVar t. Las operaciones básicas de MVar son : – newMVar :: IO (Mvar a) Crea un nuevo MVar – takeMVar :: MVar a -> IO (a) Esta primitiva se bloquea hasta que la monada tenga un valor. – putMvar :: MVar a -> a -> IO () Encapsula un valor dentro de la monada La utilidad de Mvar queda de manifiesto con un simple ejemplo 8 . despues lee dicho valor y deja la monada vacía.

newEmptyMVar . serviceConn config conn . forkIO (do { inc count . putMVar count 0 . para que el valor de dicho contador sea siempre correcto acceptConnections :: Config -> Socket -> IO () acceptConnections config socket = do { count <. forever (do { conn <. putMVar count (v+1) } dec count = do { v <.takeMVar count. tanto en escritura como en lectura. dec count}) }) } inc.dec :: MVar Int -> IO () inc count = do { v <.takeMVar count.Ejemplo MVar Supongamos que queremos que nuestros procesos actualicen el valor de un contador Debemos sincronizar a los distintos procesos. putMVar count (v-1) } 9 .accept socket .

el proceso se bloquea hasta que el MVar se vacíe Si lo que se pretendo es sacar un dato de un MVar vacio.Ejemplo Mvar NewEmptyMVar crea un nuevo MVar vacio PutMVar coloca un valor dentro de un MVar vacio y TakeMVar extrae dicho valor dejando el MVar vacio Si se pretende introducir un dato dentro de un MVar lleno. el proceso se bloquea hasta que se introduzca un dato Con todo esto se podría decir que Mvar actúa como un semáforo 10 .

pero pueden estar actuando sobre un mismo recurso. newChan :: IO (Channel a) putChan :: Channel a -> a -> IO () getChan :: Channel a -> IO a El Channel permitiría la escritura y lectura en el por parte de varios procesos sin problemas 11 .given later. por ejemplo un fichero – Necesitaríamos poder hacer que cooperasen en la escritura del fichero – Podríamos hacer que un tercer proceso sea el único que escriba en el fichero lo que los otros dos procesos le envían Esta ultima solución necesitaría de un método de envió de mensajes. Utilizando MVars podemos definir un canal de comunicación – – – – type Channel a = ...Canales y Mensajes Tomemos ahora el caso de varios procesos ejecutándose a la vez en una maquina en principio el proceso padre e hijo son independientes...

Canales Una posible implementación de los canales es utilizando dos MVars que contengan la posición de escritura y de lectura del buffer – type Channel a = (MVar (Stream a). MVar (Stream a)) 12 .

esto es un Mvar. putMVar write hole . putMVar read hole .newEmptyMVar . se modifican las posición de lectura y escritura Los datos dentro del buffer se guardan en un Stream.newEmptyMVar . hole <. uno para lectura y otro para escritura.newEmptyMVar . return (read.write) } 13 . mas otro vacio para el Stream – newChan = do { read <. write <.Canales Los Mvars son necesarios porque tanto al escribir como al leer. que o esta vacio o contiene un Item – type Stream a = MVar (Item a) Un Item es un par formado por el primer elemento del Stream junto a un Stream que contiene el resto del dato – data Item a = MkItem a (Stream a) La creación de un nuevo canal no seria mas que la creación de 2 Mvars.

takeMVar head_var . putMVar old_hole (MkItem val new_hole) } Obtener datos del canal es similar. putMVar write new_hole .takeMVar read . return val } 14 . putMVar read new_head . MkItem val new_head <.newEmptyMVar .write) = do { head_var <.write) val = do { new_hole <. teniendo en cuenta que el segundo Mvar se bloquea si el canal esta vacio – getChan (read. old_hole <.takeMVar write . reemplazarlo por el nuevo hueco e insertar en el hueco antiguo el Item – putChan (read. extraer el hueco antiguo.Canales Introducir nuevos datos en el canal seria tan simple como crear un nuevo Stream seguido de un hueco.

esta ultima es la mas utilizada con diferencia y es la que vamos a explicar 15 . especialmente si las guardas contienen lecturas y escrituras – MVars proporciona indeterminismo. por lo que puede ser utilizado para aplicaciones especificas de generador de elecciones Resumiendo.Planificación La idea básica es que cada proceso se quede bloqueado en un MVar diferente y sea el programa quien decida que proceso es el que debe ejecutarse en cada momento En Concurrent Haskell no hay un proceso que se encargue de decidir que proceso es el próximo en despertar por varias razones: – En general la elección es rara o no se una nunca – Implementar el generador de elecciones puede ser muy costoso. se utiliza en muy raras ocasiones y limita la abstracción Veamos como podemos como se puede vivir sin “selección”. al contrario de cómo se podría pensar en principio la “selección” es costosa. tenemos dos maneras de hacerlo utilizando “iterated choice” o “singular choce”.

devolverá la acción que se aplica como resultado de la ejecución de la alternativa Solo una de las alternativas recibe una respuesta al llegar al Commitment. el resto recibe nada 16 . esto se consigue haciendo que las alternativas tengan tipo. – Type Alternative a = Commitment a -> IO () – Type Commitment a = IO ( Maybe (a -> IO () )) – Data Maybe a= Nothing |a Una alternativa toma una acción de entrada/salida del tipo Commiment como argumento.Este Commitment devolverá nada si otra alternativa se ha adelantado y la nuestra tiene que ser abortada o. además es obligación del programador hacer que las elecciones sean abortables. en caso contrario.Single Choice Queremos hacer una única elección entre todas las disponibles.

Single Choice La selección podría quedar así: Select :: [Alternative a] -> IO a Select arms = newMVar >>=\result_var -> newMVar >>=\commited -> putMVar commited (Just (putMVar result_var)) >> let commit = swapMvar commited Nothing do_arm arm = forkIO (arm commit) in mapIO (do_arm commited result) arms >> takeMvar result_var 17 .

junto con otro con valor igual a si mismo o a una cola de procesos bloqueados 18 .Implementación Concurrent Haskell es una pequeña ampliación de GHC Internamente Concurrent Haskell se ejecuta como un proceso Unix. El Scheduler ejecuta los procesos durante un intervalo determinado de tiempo o hasta que el proceso se bloquea Un “thunk” (proceso retardado) es una pila con un puntero y el valor de las variables libres del proceso. Dicha variable incluye un flag que indica si el estado del Mvar es vacio o lleno. Cada invocación de ForkIO crea un nuevo proceso son su propia pila. Un MVar se implementa con un puntero a una variable mutable.

Recolector de Basura ¿Como podemos determinar cuando un proceso debe ser recogido por el recolector de basura? Un proceso será eliminado por el recolector de basura si no puede generar efectos laterales – Un proceso no se recogerá si puede generar mas entradas/salidas – Un proceso bloqueado en un Mvar puede ser eliminado si ese Mvar no es accesible por otro proceso que no sea el propio recolector de basura 19 .

The GHC Team. Tim Harris.haskell.org/ghc/docs/5.haskell. Andrew Gordon.haskell. and Maurice Herlihy. 1996 Tackling the Awkward Squad: monadic input/output. 1999 Composable Memory Transactions. Simon Peyton Jones. http://www. Prentice Hall. Simon Peyton Jones. 2005 Concurrency Basics. and foreign-language calls in Haskell.Bibliografia The implementation of functional programming languages . Conference record of POPL’96 Symposium of Principles of Programming Languages. exceptions. Tim Harris Simon Marlow Simon Peyton Jones.”Engineering theories of software construction” 2001 Using Concurrent Haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent.org/ghc/docs/5.html.html#1 20 . 1999 Haskell on a Shared-Memory Multiprocessor. Sept 2005 Control Concurrent. 1987 Concurrent Haskell.02/set/sec-using-concurrent. Simon Marlow.02/set/sec-concurrency-basics. Simon Peyton Jones. http://www. http://www. Sigbjorn Finne. Simon Peyton Jones. concurrency.html 1999 The Glorious Glasgow Haskell Compilation System User's Guide.