La concurrencia es un concepto fundamental en la programación moderna, especialmente en lenguajes funcionales como Haskell. Permite que múltiples procesos se ejecuten de manera simultánea, mejorando la eficiencia y el rendimiento de las aplicaciones. En este módulo, exploraremos cómo Haskell maneja la concurrencia y cómo puedes aprovechar sus características para escribir programas concurrentes.

Contenidos

  1. Introducción a la Concurrencia en Haskell
  2. Hilos en Haskell
  3. Comunicación entre Hilos
  4. Manejo de Estados Compartidos
  5. Ejemplos Prácticos
  6. Ejercicios y Soluciones

  1. Introducción a la Concurrencia en Haskell

La concurrencia en Haskell se basa en el uso de hilos (threads) ligeros y eficientes. Haskell proporciona varias bibliotecas para manejar la concurrencia, siendo Control.Concurrent una de las más utilizadas.

Conceptos Clave

  • Hilos (Threads): Un hilo es una unidad de ejecución que puede correr en paralelo con otros hilos.
  • MVar: Una variable mutable que puede ser compartida entre hilos.
  • STM (Software Transactional Memory): Un modelo de memoria transaccional que facilita la programación concurrente.

  1. Hilos en Haskell

Para crear y manejar hilos en Haskell, utilizamos la biblioteca Control.Concurrent. A continuación, se muestra cómo crear un hilo simple.

Ejemplo: Creación de un Hilo

import Control.Concurrent

main :: IO ()
main = do
    putStrLn "Main thread starting..."
    forkIO $ putStrLn "Hello from the new thread!"
    putStrLn "Main thread ending..."

Explicación

  • forkIO: Crea un nuevo hilo para ejecutar la acción proporcionada.
  • putStrLn: Imprime un mensaje en la consola.

En este ejemplo, el hilo principal crea un nuevo hilo que imprime un mensaje. Ambos hilos pueden ejecutarse en paralelo.

  1. Comunicación entre Hilos

Para comunicar datos entre hilos, Haskell proporciona MVar, una variable mutable que puede ser compartida entre hilos.

Ejemplo: Uso de MVar

import Control.Concurrent
import Control.Concurrent.MVar

main :: IO ()
main = do
    mvar <- newEmptyMVar
    forkIO $ do
        putStrLn "Child thread: Putting value into MVar"
        putMVar mvar "Hello from child thread"
    value <- takeMVar mvar
    putStrLn $ "Main thread: Got value from MVar: " ++ value

Explicación

  • newEmptyMVar: Crea un MVar vacío.
  • putMVar: Coloca un valor en el MVar.
  • takeMVar: Toma un valor del MVar.

En este ejemplo, el hilo principal crea un MVar y un hilo secundario coloca un valor en él. El hilo principal luego toma el valor del MVar.

  1. Manejo de Estados Compartidos

El manejo de estados compartidos puede ser complicado en programación concurrente. Haskell ofrece STM (Software Transactional Memory) para facilitar este proceso.

Ejemplo: Uso de STM

import Control.Concurrent
import Control.Concurrent.STM

main :: IO ()
main = do
    counter <- atomically $ newTVar 0
    forkIO $ incrementCounter counter
    forkIO $ incrementCounter counter
    threadDelay 1000000
    finalValue <- atomically $ readTVar counter
    putStrLn $ "Final counter value: " ++ show finalValue

incrementCounter :: TVar Int -> IO ()
incrementCounter counter = atomically $ do
    value <- readTVar counter
    writeTVar counter (value + 1)

Explicación

  • atomically: Ejecuta una transacción STM.
  • newTVar: Crea una nueva variable transaccional.
  • readTVar: Lee el valor de una variable transaccional.
  • writeTVar: Escribe un valor en una variable transaccional.

En este ejemplo, dos hilos incrementan un contador compartido utilizando STM para asegurar que las operaciones sean atómicas.

  1. Ejemplos Prácticos

Ejemplo: Productor-Consumidor

import Control.Concurrent
import Control.Concurrent.MVar

main :: IO ()
main = do
    mvar <- newEmptyMVar
    forkIO $ producer mvar
    forkIO $ consumer mvar
    threadDelay 1000000

producer :: MVar Int -> IO ()
producer mvar = do
    putMVar mvar 42
    putStrLn "Produced value: 42"

consumer :: MVar Int -> IO ()
consumer mvar = do
    value <- takeMVar mvar
    putStrLn $ "Consumed value: " ++ show value

Explicación

  • El productor coloca un valor en el MVar.
  • El consumidor toma el valor del MVar.

  1. Ejercicios y Soluciones

Ejercicio 1: Creación de Hilos

Instrucciones: Crea un programa que inicie tres hilos, cada uno de los cuales imprime un mensaje diferente.

Solución:

import Control.Concurrent

main :: IO ()
main = do
    forkIO $ putStrLn "Hello from thread 1"
    forkIO $ putStrLn "Hello from thread 2"
    forkIO $ putStrLn "Hello from thread 3"
    threadDelay 1000000

Ejercicio 2: Comunicación con MVar

Instrucciones: Crea un programa donde un hilo coloca un valor en un MVar y otro hilo lo toma y lo imprime.

Solución:

import Control.Concurrent
import Control.Concurrent.MVar

main :: IO ()
main = do
    mvar <- newEmptyMVar
    forkIO $ putMVar mvar "Hello from MVar"
    forkIO $ do
        value <- takeMVar mvar
        putStrLn $ "Received: " ++ value
    threadDelay 1000000

Conclusión

En esta sección, hemos explorado los conceptos básicos de la concurrencia en Haskell, incluyendo la creación y manejo de hilos, la comunicación entre hilos utilizando MVar, y el manejo de estados compartidos con STM. Estos conceptos son fundamentales para escribir programas concurrentes eficientes y seguros en Haskell. En el próximo módulo, profundizaremos en el paralelismo y cómo Haskell puede aprovechar múltiples núcleos de CPU para mejorar el rendimiento de las aplicaciones.

© Copyright 2024. Todos los derechos reservados