PROGRAMACIÓN CONCURRENTE EN LENGUAJES FUNCIONALES: CONCURRENT HASKELL Rafael Álvarez López Fernando Ávila Ferrer ÍNDICE   1. INTRODUCCIÓN 1.1 Motivación y Orígenes 1.2 Concurrencia y lenguajes funcionales 2.

Download Report

Transcript PROGRAMACIÓN CONCURRENTE EN LENGUAJES FUNCIONALES: CONCURRENT HASKELL Rafael Álvarez López Fernando Ávila Ferrer ÍNDICE   1. INTRODUCCIÓN 1.1 Motivación y Orígenes 1.2 Concurrencia y lenguajes funcionales 2.

PROGRAMACIÓN
CONCURRENTE EN LENGUAJES
FUNCIONALES:
CONCURRENT HASKELL
Rafael Álvarez López
Fernando Ávila Ferrer
ÍNDICE


1. INTRODUCCIÓN
1.1 Motivación y Orígenes
1.2 Concurrencia y lenguajes funcionales
2. CONCURRENT HASKELL
2.1 Procesos
2.2 Sincronización y comunicación
2.3 Semáforos
2.4 Otras operaciones concurrentes
2.5 Productor/consumidor
2.6 Canales


3. CONCLUSIONES
4. REFERENCIAS
2
1. INTRODUCCIÓN


La necesidad de ofrecer concurrencia en el acceso a
los recursos computacionales se remonta a los
primeros sistemas operativos.
Aprovechar al máximo los recursos computacionales
fue una necesidad apremiante, sobre todo en la época
en que las computadoras eran caras y escasas; el
sistema operativo tenía que ofrecer la ejecución
concurrente y segura de programas de varios
usuarios, que desde distintas terminales utilizaban un
solo procesador, y así surgió la necesidad de
introducir algunos conceptos de programación
concurrente para programar los sistemas operativos.
3
1.1 Motivación y Orígenes




Existen sistemas inherentemente concurrentes: los
sistemas reactivos.
Aparte de esta motivación existen otras dos
motivaciones principales, que de hecho fueron
anteriores en el tiempo a la utilización de la
programación concurrente para la construcción de
sistemas reactivos:
Explotación de arquitecturas paralelas para obtener
ganancia en la velocidad de ejecución (Programación
Paralela).
Mejorar la utilización del procesador (Sistemas
Operativos).
4
1.2 Concurrencia y lenguajes
funcionales

Debido a la propiedad de confluencia
(podemos encontrar dos secuencias de
evaluación diferentes que parten del mismo
dato de entrada), los lenguajes funcionales
pueden utilizar directamente el paralelismo
implícito. Otra opción es añadir al lenguaje
ciertas extensiones para especificar la
concurrencia, como es el caso que vamos a
estudiar.
5
2. CONCURRENT HASKELL

Concurrent Haskell es una extensión del
lenguaje funcional perezoso Haskell para
ofrecer la posibilidad de realizar programas
concurrentes. Concurrent Haskell representa
un nuevo paso que apunta a construir un
puente entre el mundo de los lenguajes
funcionales puros y los lenguajes intensivos
de entrada y salida.
6
Algo más que una extensión
de haskell :



Consigue integrar la concurrencia en un lenguaje perezoso. Por
ejemplo los procesos pueden comunicarse como estructuras de
datos sin evaluar unos a otros.
Presenta una semántica para Haskell claramente diferenciada
en una capa determinista y una capa concurrente. Las técnicas
de razonamiento existentes son mantenidas sin modificaciones,
por ejemplo, las transformaciones de programas que preservan
la corrección en un programa Haskell secuencial también la
preservarán en un lenguaje Haskell concurrente.
Hay pocas operaciones primitivas nuevas, que son expresivas y
fáciles de implementar.
7
nuevos ingredientes a haskell:


Procesos, y un mecanismo para gestionarlos.
Estados atómicos mutables, para soportar
comunicación y cooperación entre los
procesos.
8
2.1 Procesos


Concurrent Haskell proporciona la nueva primitiva
forkIO que inicia un proceso concurrente:
forkIO :: IO () -> IO ()
forkIO a , es una acción que toma a otra acción , a,
como su argumento y crea un proceso concurrente
que ejecuta dicha acción. La entrada y salida y
otros efectos laterales realizados por a, son
interpolados de una manera indefinida con aquellos
que siguen el forkIO.
9
Ejemplo1:
import Control.Concurrent
main = forkIO (escribir 'a') >> escribir 'b'
where escribir c = putChar c >> escribir c
10
características de forkIO :



Como la implementación de haskell usa evaluación perezosa,
forkIO necesitará sincronización entre procesos, ya que un
proceso puede intentar evaluar un razonamiento que ya está
siendo evaluado por otro proceso, en cuyo caso el primero debe
ser bloqueado hasta que el último complete la evaluación y
sobrescriba el razonamiento con su valor.
Como tanto el proceso padre como el hijo, pueden cambiar el
mismo estado compartido forkIO introduce inmediatamente el no
determinismo. El problema es que casi todas las aplicaciones
concurrentes interesantes implican el uso del mismo estado
compartido por varios procesos. Por tanto, la solución correcta
será proveer mecanismos que permitan la gestión segura de un
estado compartido.
forkIO es asimétrico. Cuando un proceso ejecuta un forkIO crea
un proceso hijo que se ejecuta concurrentemente al padre.
11
2.2 Sincronización y
comunicación


Los procesos pueden necesitar acceso
exclusivo a algunos objetos como por
ejemplo ficheros. La manera de implementar
este acceso exclusivo requiere una variable
compartida mutable o un semáforo.
Para que un proceso pueda leer una cadena
de valores producida por varios procesos es
proporcionando una operación no
determinista de ordenación.
12
Type MVar a




Un valor del tipo MVar t, para algún tipo t, es el nombre de una
posición de memoria que pueda estar vacía o contener un valor
de tipo t. A su vez sobre MVar se pueden realizar las siguientes
operaciones:
newMVar :: IO (MVar a) Crea una nueva MVar
takeMVar :: MVar a -> IO a Bloquea hasta que la posición sea
no vacía. Entonces lee y devuelve el valor dejando la posición
vacía.
putMVar :: MVar a -> a -> IO () Escribe un valor en la posición
especificada. Si existen uno o mas procesos bloqueados en
takeMVar sobre dicha variable, se selecciona uno para que lo
procese. Puede producir error utilizarlo sobre una variable que
ya contenga un valor.
13
MVar
El tipo MVar puede ser usado de tres maneras
diferentes:
 Puede ser usado como una versión sncronizada del
tipo MutVar.
 Puede ser empleado como un tipo de canal, donde
takeMVar y PutMVar funcionan como recibir y
enviar.
 El MVar puede ser usado como un semáforo
binario, donde los signal y los waits son
implementados con putMVar y takeMVar
respectivamente.
14
2.3 Semáforos
Aunque se puede implementar con un MVar usando
las operaciones putMVar y takeMVar, los semáforos
tambien están implementados en Concurrent
Haskell.
Semáforo binario:
data QSem
newQSem :: Int -> IO QSem
waitQSem :: QSem -> IO ()
signalQSem :: QSem -> IO ()
15
2.3 Semáforos
Semáforos de cantidad general:
data QSemN
newQSemN :: Int -> IO QSemN
waitQSemN :: QSemN -> Int -> IO ()
signalQSemN :: QSemN -> Int -> IO ()
16
2.4 Otras operaciones
concurrentes










forkIO :: IO a -> IO --Crea proceso hijo
threadDelay :: Int -> IO () – Dormir durante n microsegundos
newEmptyMVar :: IO (MVar a) – Crear Mvar vacío
newMVar :: a -> IO (MVar a) – Inicializar MVAr
takeMVar :: MVar a -> IO a – take bloqueante
putMVar :: MVar a -> a -> IO () – put bloqueante
tryTakeMVar :: MVar a -> IO (Maybe a) – take no bloqueante
tryPutMVar :: MVar a -> a -> IO Bool – put no bloqueante
isEmptyMVar :: MVar a -> IO Bool -- Test de Mvar vacío
swapMVar :: MVar a -> a -> IO a --- Intercambia el valor de Mvar
17
2.5 Productor/consumidor


MVar puede ser usado para implementar una
conexión entre productor y consumidor. El productor
guarda elementos en el MVar y el consumidor los
saca. El problema es que no hay nada que impida
que el productor escriba un segundo valor antes de
que el consumidor haya sacado el primero.
Este problema se soluciona con un segundo MVar
para manejar confirmaciones entre consumidor y
productor. Llamaremos a la abstracción resultante
CVar (variable de canal).
18
Productor consumidor
type CVar a = (MVar a,
MVar ())
-- Productor-> consumidor
-- Consumidor -> productor
newCVar :: IO (CVar a)
newCVar = newMVar >>= \ data_var ->
newMVar
>>= \ ack_var ->
putMVar ack_var () >> return (data_var, ack_var)
putCVar :: CVar a -> a -> IO ()
putCVar (data_var,ack_var) val= takeMVar ack_var >> putMVar
data_var val
getCVar :: CVar a -> IO a
getCVar (data_var,ack_var) = takeMVar data_var
putMVar ack_var ()
>> return val
>>= \ val ->
19
2.6 Canales

El proceso creado por el forkIO y su padre
pueden realizar entrada y salida
independientemente. Podemos pensar en el
estado del sistema como un objeto
conmutable compartido. Por ejemplo, si dos
procesos escriben en el mismo fichero
pueden aparecer situaciones indeseadas.
Pero como ya sabemos, es común que dos o
más procesos quieran acceder al mismo
fichero.
20
Canales
Usando MVar’s podemos definir un nuevo tipo que
llamaremos canal que permitirá que múltiples
procesos escriban y lean de él de forma segura.
type Channel a = (MVar (Stream a), -- Último leído
MVar (Stream a)) -- Último escrito
 newChan :: IO (Channel a)
 putChan :: Channel a -> a -> IO
 getChan :: Channel a -> IO a
21
Canales
22



El canal está implementado por un par de MVar’s
que apuntan al último elemento leído (read end) y al
último elemento escrito (write end) del canal
respectivamente.
Las Mvar del canal proporcionan un mecanismo
mediante el cual las operaciones de put y get
pueden modificar el read end y el write end del
canal.
Los datos en el buffer son almacenados en un
Stream que es un MVar que puede estar vacío (en
cuyo caso no hay datos en el stream) o almacena
un Item.
23


type Stream a = MVar (Item a)
Un Item es simplemente un par de elementos, uno
con el dato y otro apundanto al resto del stream.
data Item a = MkItem a (Stream a)
Un Stream puede ser considerado como una lista
que consiste en alternar Items y Mvars que termina
con un “agujero” que consiste en un MVar vacío. El
Write end del canal apunta a dicho “agujero”(Hole).
24
newChan = do { read <- newEmptyMVar ;
write <- newEmptyMVar ;
hole <- newEmptyMVar ;
putMVar read hole ;
putMVar write hole ;
return (read,write) }
25
putChan (read,write) val
= do { new_hole <- newEmptyMVar ;
old_hole <- takeMVar write ;
putMVar write new_hole ;
putMVar old_hole (MkItem val new_hole) }
26
getChan (read,write)
= do { head_var <- takeMVar read ;
MkItem val new_head <- takeMVar head_var ;
putMVar read new_head ;
return val }
27
canal multicast
dupChan :: Channel a -> IO (Channel a)
dupChan (read,write)
= do { new_read <- newEmptyMVar ;
hole <- readMVar write ;
putMVar new_read hole ;
return (new_read, write) }
28
Implementación de canales en
Concurrent Haskell









data Chan a
newChan :: IO (Chan a)
-- Nuevo canal
writeChan :: Chan a -> a -> IO () --Escribe dato en canal
readChan :: Chan a -> IO a
--Lee un dato del canal
dupChan :: Chan a -> IO (Chan a) --Duplica el canal
unGetChan :: Chan a -> a -> IO () --Devuelve un dato al canal
isEmptyChan :: Chan a -> IO Bool --Comprueba si está vacío
getChanContents :: Chan a -> IO [a] -- lee todo el contenido
writeList2Chan :: Chan a -> [a] -> IO () -- Escribe el contenido
de una lista en el canal
29
3. CONCLUSIONES



Hemos comprobado como Concurrent Haskell
aporta nueva funcionalidad a Haskell.
Añadiendo forkIO y MVars a Haskell damos un salto
cualitativo en cuanto a la cantidad de aplicaciones
que podemos escribir. Las extensiones son sencillas
y simples de describir.
En la actualidad Haskell y sus versiones
concurrentes están siendo empleados de manera
comercial como herramienta de programación para
servidores, aportando una nueva manera de
programar potente y elegante.
30
4. REFERENCIAS







http://www.haskell.org
Ruíz, B.C.; Gutiérrez, F.; Guerrero, P.; Gallardo, J.E.: Razonando con
Haskell. Una introducción a la Programación Funcional. Servicio
reprogr. (OCÉ) E.T.S.I.I., Universidad de Málaga. 2000
http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control.Concu
rrent.html
Tackling the Awkward Squad: monadic input/output, concurrency,
exceptions, and foreign-language calls in Haskell
Simon PEYTON JONES Microsoft Research, Cambridge
[email protected]
http://research.microsoft.com/users/simonpj
Concurrent Haskell
Simon Peyton Jones (University of Glasgow)
Andrew Gordon (University of Cambridge)
Sigbjorn Finne (University of Glasgow)
Notas para la asignatura Programación Declarativa Avanzada.
Blas Carlos Ruiz Jiménez
31