La programación multihilo y paralela es una técnica avanzada que permite a los programas ejecutar múltiples tareas simultáneamente, mejorando el rendimiento y la eficiencia. En este módulo, aprenderemos los conceptos básicos y avanzados de la programación multihilo y paralela en C#.

Contenidos

  1. Introducción al Multihilo
  2. Creación y Gestión de Hilos
  3. Sincronización de Hilos
  4. Programación Paralela con TPL (Task Parallel Library)
  5. PLINQ (Parallel LINQ)
  6. Ejercicios Prácticos

  1. Introducción al Multihilo

El multihilo permite que un programa realice múltiples operaciones al mismo tiempo. Cada hilo puede ejecutar una tarea diferente, lo que puede mejorar significativamente el rendimiento de aplicaciones que realizan operaciones intensivas en CPU o I/O.

Conceptos Clave

  • Hilo (Thread): La unidad más pequeña de procesamiento que puede ser programada por un sistema operativo.
  • Proceso: Un programa en ejecución que puede contener uno o más hilos.
  • Sincronización: Mecanismos para coordinar la ejecución de hilos y evitar condiciones de carrera.

  1. Creación y Gestión de Hilos

En C#, los hilos se pueden crear y gestionar utilizando la clase Thread del espacio de nombres System.Threading.

Ejemplo Básico

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(new ThreadStart(DoWork));
        thread.Start();
        thread.Join(); // Espera a que el hilo termine
        Console.WriteLine("Hilo principal terminado.");
    }

    static void DoWork()
    {
        Console.WriteLine("Hilo secundario en ejecución.");
    }
}

Explicación

  • ThreadStart: Delegado que representa el método que se ejecutará en el hilo.
  • Start(): Inicia la ejecución del hilo.
  • Join(): Bloquea el hilo principal hasta que el hilo secundario termine.

  1. Sincronización de Hilos

La sincronización es crucial para evitar problemas como condiciones de carrera y asegurar que los recursos compartidos se manejen correctamente.

Bloqueo con lock

using System;
using System.Threading;

class Program
{
    private static readonly object _lock = new object();
    private static int _counter = 0;

    static void Main()
    {
        Thread thread1 = new Thread(IncrementCounter);
        Thread thread2 = new Thread(IncrementCounter);

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();

        Console.WriteLine($"Contador final: {_counter}");
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            lock (_lock)
            {
                _counter++;
            }
        }
    }
}

Explicación

  • lock: Asegura que solo un hilo pueda acceder a la sección crítica del código a la vez.
  • _lock: Objeto utilizado para bloquear la sección crítica.

  1. Programación Paralela con TPL (Task Parallel Library)

La TPL simplifica la creación y gestión de tareas paralelas.

Ejemplo Básico con Task

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Task task = Task.Run(() => DoWork());
        task.Wait(); // Espera a que la tarea termine
        Console.WriteLine("Tarea completada.");
    }

    static void DoWork()
    {
        Console.WriteLine("Tarea en ejecución.");
    }
}

Explicación

  • Task.Run: Crea y ejecuta una nueva tarea.
  • Wait(): Bloquea el hilo principal hasta que la tarea termine.

  1. PLINQ (Parallel LINQ)

PLINQ permite realizar consultas LINQ en paralelo, aprovechando múltiples núcleos de CPU.

Ejemplo Básico

using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var numbers = Enumerable.Range(1, 1000000);
        var evenNumbers = numbers.AsParallel().Where(n => n % 2 == 0).ToArray();

        Console.WriteLine($"Números pares encontrados: {evenNumbers.Length}");
    }
}

Explicación

  • AsParallel(): Convierte una secuencia en una consulta paralela.
  • Where: Filtra los elementos de la secuencia.

  1. Ejercicios Prácticos

Ejercicio 1: Crear y Gestionar Hilos

Objetivo: Crear dos hilos que incrementen un contador compartido de manera segura.

Código Inicial:

using System;
using System.Threading;

class Program
{
    private static int _counter = 0;

    static void Main()
    {
        // Crear y empezar hilos aquí
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            // Incrementar contador aquí
        }
    }
}

Solución:

using System;
using System.Threading;

class Program
{
    private static readonly object _lock = new object();
    private static int _counter = 0;

    static void Main()
    {
        Thread thread1 = new Thread(IncrementCounter);
        Thread thread2 = new Thread(IncrementCounter);

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();

        Console.WriteLine($"Contador final: {_counter}");
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            lock (_lock)
            {
                _counter++;
            }
        }
    }
}

Ejercicio 2: Uso de TPL

Objetivo: Crear una tarea que calcule la suma de los números del 1 al 1000.

Código Inicial:

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        // Crear y ejecutar tarea aquí
    }

    static void CalculateSum()
    {
        // Calcular suma aquí
    }
}

Solución:

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Task task = Task.Run(() => CalculateSum());
        task.Wait();
        Console.WriteLine("Tarea completada.");
    }

    static void CalculateSum()
    {
        int sum = 0;
        for (int i = 1; i <= 1000; i++)
        {
            sum += i;
        }
        Console.WriteLine($"Suma: {sum}");
    }
}

Conclusión

En este módulo, hemos cubierto los conceptos fundamentales y avanzados de la programación multihilo y paralela en C#. Aprendimos a crear y gestionar hilos, sincronizar hilos para evitar condiciones de carrera, y utilizar la TPL y PLINQ para realizar tareas en paralelo de manera eficiente. Estos conocimientos son esenciales para desarrollar aplicaciones que requieran un alto rendimiento y eficiencia.

En el próximo módulo, exploraremos la entrada/salida de archivos en C#, lo que nos permitirá manejar datos de manera persistente.

© Copyright 2024. Todos los derechos reservados