El blog del burgués

22 febrero 2010

Hilos en C#: Conceptos

Filed under: C# — elburgues @ 11:22 PM
Tags:

C# soporta la ejecución paralela de código a través de múltiples hilos. Un hilo es una ruta independiente de ejecución, capaz de ejecutarse simultáneamente con otros hilos. Un programa C# arranca en un hilo principal creado automáticamente por el CLR y el sistema operativo y puede estar compuesto de múltiples subprocesos mediante la creación de hilos adicionales.He aquí un ejemplo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
class PruebaHilos
{
static void Main()
{
Thread t = new Thread(EscribeY);
t.Start(); // Arranca EscribeY en un hilo nuevo.
while (true) Console.Write(“x”); // Escribe ‘x’ indefinidamente.
}
static void EscribeY()
{
while (true) Console.Write(“y”); // Escribe ‘y’ indefinidamente.
}
}

El hilo principal crea un nuevo hilo ‘t’ en el cual se va a ejecutar un método que imprime el carácter ‘y’ repetidas veces en la consola. Simultáneamente, el hilo principal hace lo mismo con el carácter ‘x’. El CLR asigna a cada hilo su propia pila de memoria de forma que las variables locales permanecen separadas. En el siguiente ejemplo se define un método con una variable local y se llama al método simultáneamente tanto desde el hilo principal como del hilo recién creado:

class PruebaHilos
{
static void Main()
{
new Thread(VamosAlla).Start(); // Llama a VamosAlla() en un nuevo hilo.
VamosAlla(); // Llama a VamosAlla() en el hilo principal.
}
static void VamosAlla()
{
// Declara y usa la variable local ciclos
for (int ciclos = 0; ciclos < 5; ciclos++) Console.Write(‘?’);
}
}

Una copia separada de la variable “ciclos” es creada en cada pila de memoria de cada hilo, de forma que el carácter ‘?’ se imprime 10 veces:

Los hilos comparten datos si tienen una referencia común a la misma instancia, por ejemplo:

class PruebaHilos
{
bool hecho;
static void Main()
{
PruebaHilos ph = new PruebaHilos(); // Crea una instancia común.
new Thread(ph.VamosAlla).Start();
ph.VamosAlla();
}
// Date cuenta de que VamosAlla() es ahora un método de instancia.
void VamosAlla()
{
if (!hecho) { hecho = true; Console.WriteLine(“Hecho”); }
}
}

Al llamar ambos hilos al método VamosAlla() sobre la misma instancia de PruebaHilos, comparten el campo “hecho”. Esto hace que se imprima el mensaje “Hecho” una vez en vez de dos veces:

Los campos estáticos ofrecen otra manera de compartir datos entre hilos. Éste es el mismo ejemplo con el campo “hecho” estático:

class PruebaHilos
{
static bool hecho; //Los campos estáticos son compartidos entre diferentes hilos.
static void Main()
{
new Thread(VamosAlla).Start();
VamosAlla();
}
static void VamosAlla()
{
if (!hecho) { hecho = true; Console.WriteLine(“Hecho”); }
}
}

Los dos últimos ejemplos ilustran otro concepto clave, el de la seguridad de procesos y es que la salida que se produce es actualmente indeterminada. Incluso es posible, aunque poco probable que la salida “Hecho” pudiera ser pintada dos veces por consola. Sin embargo, si cambiamos el orden de las instrucciones que contiene VamosAlla(), las posibilidades de que la salida “Hecho” se imprima dos veces aumentan de manera considerable:

static void VamosAlla()
{
if (!hecho) { Console.WriteLine(“Hecho”); hecho = true; }
}

El problema es que un hilo puede estar evaluando la condición mientras que el otro hilo está ejecutando ya la sentencia que imprime en la consola antes de que tuviera la oportunidad de cambiar el valor de la variable “hecho”. La solución es obtener un bloqueo exclusivo mientras se lee o se escribe en el campo común. C# proporciona la sentencia lock para éste propósito:

class PruebaHilos
{
static bool hecho;
static object bloqueador = new object();
static void Main()
{
new Thread(VamosAlla).Start();
VamosAlla();
}
// Date cuenta de que VamosAlla() es ahora un método de instancia.
static void VamosAlla()
{
lock (bloqueador)
{
if (!hecho) { Console.WriteLine(“Hecho”); hecho = true; }
}
}
}

Cuando dos hilos sostienen simultáneamente un bloqueo, un hilo espera o se bloquea, hasta que el bloqueo se deshace y todo vuelve a estar disponible. Con esto nos aseguramos de que solamente un hilo puede entrar en la sección crítica de código a un mismo tiempo, y el contenido de la variable “hecho” será impreso en la consola siempre una sola vez. El código fuente protegido de esta manera, frente a indeterminaciones en un contexto multihilo es llamado seguridad de hilos.

Pausar temporalmente o bloquear, es una característica esencial en la coordinación o sincronización de las actividades de los hilos. Esperar a que se libere un bloqueo exclusivo es una razón por la que un hilo puede bloquearse. Otra razón es que un hilo quiera hacer una pausa (un hilo, mientras está pausado, no consume recursos de cpu). Se puede hacer así:

Thread.Sleep (TimeSpan.FromSeconds (30)); // Pausa de 30 segundos.

Se puede hacer que un hilo también espere a que otro hilo termine de ejecutarse por completo, usando el método Join:

Thread t = new Thread(VamosAlla);
t.Start();
t.Join(); // La ejecución se detendrá aquí hasta que el hilo ‘t’ finalice.

Funcionamiento

Los hilos son manejados internamente por un programador o “gestor” de hilos, una función que el CLR típicamente delega en el sistema operativo. Un programador de hilos asegura que a todos los hilos se les asigna un tiempo de ejecución apropiado y que todos aquellos hilos que estén bloqueados o pausados no consuman tiempo de CPU. En un ordenador con un solo procesador, un programador de hilos gestiona una alternancia rápida entre los hilos activos, derivando en un comportamiento entrecortado (como se puede ver en la 1ª pantalla de consola de este artículo), dónde cada bloque de ‘x’ o de ‘y’ consecutivas se corresponde con una asignación de tiempo concreta para un hilo u otro. En Windows XP, la asignación de tiempo para cada hilo está en el orden de las decenas de milisegundos.

Hilos y procesos

Todos los hilos dentro de una aplicación están dentro de un proceso. Hilos y procesos tienen similitudes, por ejemplo, la ejecución de procesos también es alternada con otros procesos de la misma manera que sucede con los hilos dentro de una aplicación C#. La diferencia principal es que los procesos están completamente aislados unos de otros, sin embargo, los hilos comparten memoria con otros hilos que pertenecen al mismo proceso. Eso es lo que hace que los hilos sean útiles. Un hilo puede estar generando datos en un segundo plano mientras que otro hilo muestra los datos que van llegando.

¿Cuándo usar hilos?

Normalmente se utilizan para realizar trabajos en un segundo plano que típicamente vayan a consumir mucho tiempo hasta que se completen, por ejemplo, operaciones masivas con bases de datos, operaciones de cálculo…, mientras que la interfaz de usuario queda libre para poder trabajar en otra cosa. De otra manera, ésta no respondería, se vería bloqueada, aumentando la insatisfacción del usuario. También es posible que desde dicha interfaz pueda ofrecerse al usuario la posibilidad de cancelar este tipo de operaciones u ofrecer incluso información acerca del estado o porcentaje de operación completado. Una clase que puede resultar muy útil para este tipo de situaciones es la clase BackGroundWorker.

No todo son ventajas manejando hilos. La interacción entre hilos puede ser compleja y sin son usados en exceso o inadecuadamente, conllevan una penalización en el rendimiento de la CPU.

En ésta entrada he hablado acerca de cómo manejar las excepciones cuando se trata con hilos.

Anuncios

6 comentarios »

  1. muy bien explikado, muchas gracias.

    Comentario por javier — 25 marzo 2010 @ 6:14 AM | Responder

  2. Excelente la explicación… me viene bien para repasar lo que había dejado sin usar. Gracias!

    Comentario por TROMEX — 22 mayo 2010 @ 6:57 AM | Responder

  3. eres una reatotota cabron, gracias entendi a laperfeccion lo que son lo hilos!!! xD

    Comentario por felipon — 19 septiembre 2010 @ 2:03 AM | Responder

  4. esta bien la explicación, muy parcial y básica, pero este tema de hilos es verdaderamente extenso y en muchos de los casos complicado. gracias por el aporte, esta bueno e interesante.

    Comentario por Keneth — 1 noviembre 2010 @ 4:27 AM | Responder

  5. Muy bien muchas gracias

    Comentario por daniel — 15 febrero 2012 @ 11:01 PM | Responder

  6. Excelente articulo.

    Comentario por Jorge — 26 diciembre 2012 @ 10:27 PM | Responder


RSS feed for comments on this post.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Crea un blog o un sitio web gratuitos con WordPress.com.

A %d blogueros les gusta esto: