Vous êtes sur la page 1sur 18

Programacin concurrente (MultiHilos)

Jorge Luis lvarez Cruz


Tpicos Avanzados de Programacin

Lic. ngel Len Ramos

26/04/2012

Al escuchar la palabra multi-hilo, tal vez lo primero que te viene a la mente son muchos "hilos" de los que conocemos normalmente en casa, pero al hablar en trminos de programacin, no nos estamos refiriendo a esos "hilos". En programacin, nos estamos refiriendo a los lenguajes de programacin que permiten la ejecucin de varias tareas en forma simultnea. Por ejemplo, consideremos la cantidad de aplicaciones que corren a la vez dentro de un mismo entorno grfico. Mientras una persona escribe un documento, est corriendo Microsoft Windows adems de Internet Explorer, Windows Explorer, CD Player y el Control de Volumen. Estas aplicaciones son ejecutadas dentro de alguna versin de Windows. De esta forma, podemos pensar que los procesos son anlogos a las aplicaciones o a programas aislados, pero realmente tiene asignado espacio propio de ejecucin dentro del sistema.

Los hilos o threads, son bsicamente, pequeos procesos o piezas independientes de un gran proceso. Tambin podemos decir, que un hilo es un flujo nico de ejecucin dentro de un proceso (un proceso es un programa ejecutndose dentro de su propio espacio de direcciones). Un hilo no puede correr por s mismo, se ejecuta dentro de un programa, ya que requieren la supervisin de un proceso padre para correr. Se pueden programar mltiples hilos de ejecucin para que corran simultneamente en el mismo programa. La utilidad de la programacin multihilo resulta evidente. Por ejemplo, un navegador Web puede descargar un archivo de un sitio, y acceder a otro sitio al mismo tiempo. Si el navegador puede realizar simultneamente dos tareas, no tendr que esperar hasta que el archivo haya terminado de descargarse para poder navegar a otro sitio. Los hilos a menudo, son conocidos o llamados procesos ligeros.

Programas de flujo nico:

Un programa de flujo nico, tarea nica o mono -hilo (single-thread) utiliza un


nico flujo de control (thread) para controlar su ejecucin. Muchos programas no necesitan la potencia o utilidad de mltiples tareas. Sin necesidad para especificar explcitamente que se quiere un nico flujo de control, muchos de los applets y aplicaciones son de flujo nico.

Por ejemplo, en la aplicacin: public class HolaMundo{ public static void main (string args[]){ System.out.println( " Hola Mundo " ); } }

Aqu, Cuando se llama al main(), la aplicacin imprime el mensaje y termina. Esto ocurre dentro de una nica tarea (thread). Debido a que la mayor parte de los entornos operativos no solan ofrecer un soporte razonable para mltiples tarea, los leguajes de programacin tradicionales, tales como c++, no incorporaron mecanismo para describir de manera elegante situaciones de este tipo. La sincronizacin entre las mltiples partes de un programa se lleva a cabo a medida de un bucle de sucesin nico. Estos entornos son de tipo sncrono, gestionados por sucesos. Entornos tales como el de Mac OS de Apple, Windows de Microsft y X11/Motif fueron

diseados al entorno del bucle del suceso.

Programas de flujo mltiple: El beneficio de ser multihilo, consiste en un mejor rendimiento interactivo y un mejor comportamiento en tiempo real. Aunque el comportamiento en tiempo real, est limitado a las capacidades del sistema operativo sobre el que corre, an supera a los entornos de flujo nico de programa (single-thread) tanto en facilidad de desarrollo, como en rendimiento. Mientras los procesos mantienen su propio espacio de direcciones y entorno de operaciones, los hilos dependen de un programa padre en lo que se refiere a recursos de ejecucin. Las aplicaciones (y applets) multitarea utiliza muchos contextos de ejecucin para cumplir su trabajo. Se aprovecha del hecho de que muchas tareas contienen subtareas distintas e independientes. Se puede utilizar un hilo de ejecucin para cada subtarea. Mientras que los programas de flujo nico pueden realizar su tarea ejecutando las subtareas secuencialmente, un programa multitarea permite que cada tarea comience y termine tan pronto como sea posible. Este comportamiento presenta una mejor respuesta a la entrada en tiempo real.

C# ofrece un mecanismo muy sencillo de implementar hilos, basado en la utilizacin de la clase Thread. El constructor de esta clase recibe como parmetro el mtodo o funcin que hay que ejecutar en paralelo. Este parmetro se indica mediante la utilizacin de un delegado, que es el mecanismo que, entre otras cosas, se utiliza en .NET para utilizar punteros a funciones de forma segura. La firma del delegado no incluye ningn parmetro, por lo que nicamente es posible crear hilos de forma directa sobre mtodos y funciones que no requieran parmetros de entrada ni de salida. En los siguientes ejemplos muestro un caso sencillo de creacin de un hilo y otro en el que explico una forma de poder crear un hilo con entrada y salida de parmetros. En el siguiente ejemplo se dispone de una clase con dos mtodos que muestran mensajes por pantalla. El objetivo es crear dos hilos, uno para cada uno de los mtodos y ejecutarlos de forma paralela, de forma que podamos ver como resultado cmo se van intercalando los mensajes escritos por cada mtodo. using System; using System.IO; using System.Threading;

public class Mensajes{ public void Mostrar1() { for(int i=0;i<10;i++){ Console.WriteLine("Escribiendo desde ==> 1"); Thread.Sleep(1000); } }

public void Mostrar2()

{ for(int i=0;i<10;i++){ Console.WriteLine("Escribiendo desde ==> 2"); Thread.Sleep(1000); } } }

public class Ejemplo{

public static void Main() { Mensajes msg = new Mensajes();

Thread th1 = new Thread(new ThreadStart(msg.Mostrar1)); Thread th2 = new Thread(new ThreadStart(msg.Mostrar2));

th1.Start(); th2.Start();

th1.Join(); th2.Join(); }

} La creacin de cada hilo se realiza mediante las lneas Thread th1 = new Thread(new ThreadStart(msg.Mostrar1));. Esta lnea indica que se crea una instancia de la clase Thread, con nombre th1, a partir de un delegado de la clase ThreadStart, que apunta al mtodo Mostrar1 del objeto msg creado anteriormente. Una vez creados los dos hilos hay que activarlos, para lo que se llama al mtodo Start de cada uno de ellos. Tras este punto cada hilo se ejecuta en paralelo entre s, y con el programa principal, por lo que utilizamos el mtodo

Join de ambos hilos para esperar a que terminen los hilos antes de finalizar el programa. El delegado ThreadStart no acepta parmetros de entrada ni de salida, por lo que si queremos crear un hilo sobre un mtodo que los necesite, hay que utilizar algn mecanismo auxiliar. Una posible forma de conseguir esto es crear una nueva clase con los parmetros necesarios en la entrada y con un nuevo mtodo sin parmetros que llame al mtodo que queremos hacer paralelo, envindole estos parmetros. A partir de aqu tendramos que crear una instancia de dicha clase con los parmetros que queremos enviar al mtodo original, y hacer que el hilo se ejecutase sobre el nuevo mtodo de la clase. En el caso de que quisiramos obtener el resultado de la ejecucin, deberamos crear una funcin que acepte como parmetro de entrada el tipo del valor devuelto por el mtodo original, y hacer que la nueva clase creada disponga tambin de un delegado que indique la funcin a la que llamar tras la ejecucin. Como esto puede parecer un poco lioso, vamos a ver otro ejemplo. En esta ocasin disponemos de una clase de funciones matemticas y queremos llamar de forma paralela a una de ellas. Este mtodo acepta un valor entero en la entrada y devuelve otro entero. using System; using System.Threading; using System.IO;

public class EjemploMates{

public static int CalculoComplejo(int n) { // sumo uno y espero 5 segundos n = n+1; Thread.Sleep(5000); return n; }

public class HiloParaMates{ protected int n; protected MatesCallback callback = null; public HiloParaMates(int n, MatesCallback callback){ this.n = n; this.callback = callback; } public void CalculoComplejo() { int result = EjemploMates.CalculoComplejo(n); if(callback != null) callback(result); } }

// Creo un delegado con la firma necesaria para capturar // El valor devuelto por el mtodo CalculoComplejo public delegate void MatesCallback(int n);

public class Ejemplo{

public static void Main() { HiloParaMates hpm = new HiloParaMates(1000, new

MatesCallback(ResultCallback));

Thread th = new Thread(new ThreadStart(hpm.CalculoComplejo));

th.Start(); th.Join();

public static void ResultCallback(int n) { Console.WriteLine("Resultado de la operacin: "+n); } } En el anterior cdigo la clase HiloParaMates es la que nos permite encapsular la llamada al mtodo EjemploMates.Calcular. Este mtodo requiere un parmetro de tipo entero, por lo que la clase requiere este parmetro en su constructor. Adems se requiere en el constructor otro parmetro ms, un delegado MatesCallback, que acepta un entero en la entrada. La idea es que tras realizar el clculo se llame al mtodo que se indique proporcionndole el resultado. Para hacer funcionar todo esto, en Main se crea una instancia de la clase HiloParaMates indicndole que queremos utilizar el valor numrico 1000 y que se llame al mtodo (esttico) ResultCallback cuando se obtenga el resultado. Para crear el hilo es suficiente con indicar que se quiere hacer sobre el mtodo CalculoComplejo de la instancia hpm.

En las secciones siguientes se describen las caractersticas y clases que se pueden utilizar para sincronizar el acceso a recursos en aplicaciones multiproceso. Una de las ventajas de utilizar varios subprocesos en una aplicacin es que cada subproceso se ejecuta de forma asincrnica. En las aplicaciones para Windows, esto permite realizar las tareas que exigen mucho tiempo en segundo plano mientras la ventana de la aplicacin y los controles siguen respondiendo. En las aplicaciones de servidor, el subprocesamiento mltiple proporciona la capacidad de controlar cada solicitud de entrada con un subproceso diferente. De lo contrario, no se atendera cada nueva solicitud hasta que se hubiera satisfecho totalmente la solicitud anterior. Sin embargo, la naturaleza asincrnica de los subprocesos significa que el acceso a recursos como identificadores de archivos, conexiones de red y memoria se debe coordinar. De lo contrario, dos o ms subprocesos podran tener acceso al mismo tiempo al mismo recurso, cada uno desprevenido de las acciones del otro. El resultado seran daos imprevisibles en los datos. Para las operaciones simples en tipos de datos numricos enteros, la sincronizacin de subprocesos se puede lograr con miembros de la clase Interlocked. Para todos los dems tipos de datos y los recursos no seguros para subprocesos, el subprocesamiento mltiple slo se puede realizar sin ningn riesgo utilizando las estructuras de este tema. La palabra clave lock: La palabra clave lock se puede utilizar para garantizar que un bloque de cdigo se ejecuta hasta el final sin que lo interrumpan otros subprocesos. Esto se logra obteniendo un bloqueo de exclusin mutua para un objeto determinado durante la ejecucin de un bloque de cdigo.

Una instruccin lock comienza con la palabra clave lock, que utiliza un objeto como argumento, seguida de un bloque de cdigo que slo un subproceso puede ejecutar a la vez. Por ejemplo: C# public void Function() { System.Object lockThis = new System.Object(); lock(lockThis) { // Access thread-sensitive resources. } } El argumento suministrado a la palabra clave lock tiene que ser un objeto basado en un tipo de referencia y se utiliza para definir el mbito del bloqueo. En el ejemplo anterior, el mbito del bloqueo se limita a esta funcin porque no existe ninguna referencia al objeto fuera de la funcin. Estrictamente, el objeto suministrado a lock slo se utiliza para identificar nicamente el recurso que varios subprocesos comparten, de modo que puede ser una instancia de clase arbitraria. Sin embargo, en la prctica, este objeto normalmente representa el recurso para el que la sincronizacin de subprocesos es necesaria. Por ejemplo, si varios subprocesos van a utilizar un objeto contenedor, se puede pasar el contenedor para bloquearlo. Entonces, el bloque de cdigo sincronizado que sigue al bloqueo tendra acceso al contenedor. Con tal de que otros subprocesos bloqueen el mismo contenedor antes de tener acceso a l, el acceso al objeto se sincroniza de forma segura. Generalmente, es mejor evitar el bloqueo en un tipo public o en instancias de objeto que estn fuera del control de la aplicacin. Por

ejemplo, lock(this) puede ser problemtico si se puede tener acceso a la instancia pblicamente, ya que el cdigo que est fuera de su control tambin puede bloquear el objeto. Esto podra crear situaciones del interbloqueo, en las que dos o ms subprocesos esperan a que se libere el mismo objeto. El

bloqueo de un tipo de datos pblico, como opuesto a un objeto, puede producir problemas por la misma razn. El bloqueo de cadenas literales es especialmente arriesgado porque el Common Language Runtime

(CLR) interna las cadenas literales. Esto significa que hay una instancia de un literal de cadena determinado para todo el programa, exactamente el mismo objeto representa el literal en todos los dominios de la aplicacin en ejecucin, en todos los subprocesos. Como resultado, un bloqueo sobre una cadena que tiene el mismo contenido en cualquier parte del proceso de la aplicacin bloquea todas las instancias de esa cadena en la aplicacin. Por tanto, es mejor bloquear un miembro privado o protegido que no est internado. Algunas clases proporcionan especficamente los miembros para bloquear. Por ejemplo, el tipo Array proporciona SyncRoot. Muchos tipos de coleccin tambin proporcionan un miembro SyncRoot. Monitores Al igual que la palabra clave lock, los monitores evitan que varios subprocesos ejecuten simultneamente bloques de cdigo. El mtodo Enter permite que un subproceso, y slo uno, continu con las instrucciones siguientes; todos los dems subprocesos se bloquean hasta que el subproceso en ejecucin llama a Exit. Esto es similar a utilizar la palabra clave lock. De hecho, la palabra clave lock se implementa con la clase Monitor. Por ejemplo: C# lock(x) { DoSomething(); }

Esto equivale a: C# System.Object obj = (System.Object)x;

System.Threading.Monitor.Enter(obj); try { DoSomething(); } finally { System.Threading.Monitor.Exit(obj); }

Normalmente, es preferible utilizar la palabra clave lock en vez de utilizar directamente la clase Monitor, porque lock es ms conciso y garantiza que se libera el monitor subyacente aunque el cdigo protegido produzca una excepcin. Esto se logra con la palabra clave finally, que ejecuta su bloque de cdigo asociado independientemente de que se produzca una excepcin. Eventos de sincronizacin y controladores de espera El uso de un bloqueo o un monitor es til para evitar la ejecucin simultnea de bloques de cdigo utilizados por varios subprocesos, pero estas construcciones no permiten que un subproceso comunique un evento a otro. Esto requiere eventos de sincronizacin, que son objetos que tienen uno de dos estados (sealizado y no sealizado) y se pueden utilizar para activar y suspender subprocesos. Los subprocesos se pueden suspender haciendo que esperen a que se produzca un evento de sincronizacin que no est sealizado y se pueden activar cambiando el estado del evento a sealizado. Si un subproceso intenta esperar a que se produzca un evento que ya est sealizado, el subproceso se sigue ejecutando sin retraso. Hay dos tipos de eventos Slo difieren de en

sincronizacin: AutoResetEvent y ManualResetEvent.

que AutoResetEvent cambia automticamente de sealizado a no sealizado siempre que activa un subproceso. A la inversa, ManualResetEvent permite

que cualquier nmero de subprocesos est activado si su estado es sealizado y slo vuelve al estado no sealizado cuando se llama a su mtodo Reset. Se puede hacer que los subprocesos esperen a que se produzcan eventos si se llama a uno de los mtodos de espera,

como WaitOne, WaitAny o WaitAll.System.Threading.WaitHandle.WaitOne hac e que el subproceso espere hasta que se sealice un nico

evento, System.Threading.WaitHandle.WaitAny bloquea un subproceso hasta que se sealicen uno o varios eventos especificados

y System.Threading.WaitHandle.WaitAll bloquea el subproceso hasta que se sealicen todos los eventos indicados. Un evento se sealiza cuando se llama a su mtodo Set. En el ejemplo siguiente, la funcin Main crea e inicia un subproceso. El nuevo subproceso espera a que se produzca un evento mediante el mtodo WaitOne. Se suspende el subproceso hasta que el evento sea sealizado por el subproceso primario que est ejecutando la funcin Main. Cuando el evento se sealiza, vuelve a ejecutarse el subproceso auxiliar. En este caso, como el evento slo se utiliza para una activacin del subproceso, se podran utilizar las clases AutoResetEvent o ManualResetEvent. C# using System; using System.Threading;

class ThreadingExample { static AutoResetEvent autoEvent;

static void DoWork() { Console.WriteLine(" worker thread started, now waiting on event..."); autoEvent.WaitOne(); Console.WriteLine(" worker thread reactivated, now exiting...");

static void Main() { autoEvent = new AutoResetEvent(false);

Console.WriteLine("main thread starting worker thread..."); Thread t = new Thread(DoWork); t.Start();

Console.WriteLine("main thrad sleeping for 1 second..."); Thread.Sleep(1000);

Console.WriteLine("main thread signaling worker thread..."); autoEvent.Set(); } }

Objeto Mutex Una exclusin mutua es similar a un monitor; impide la ejecucin simultnea de un bloque de cdigo por ms de un subproceso a la vez. De hecho, el nombre "mutex" es una forma abreviada del trmino "mutuamente exclusivo". Sin embargo, a diferencia de los monitores, una exclusin mutua se puede utilizar para sincronizar los subprocesos entre varios procesos. Una exclusin mutua se representa mediante la clase Mutex. Cuando se utiliza para la sincronizacin entre procesos, una exclusin mutua se denomina una exclusin mutua con nombre porque va a utilizarla otra aplicacin y, por tanto, no se puede compartir por medio de una variable global o esttica. Se debe asignar un nombre para que ambas aplicaciones puedan tener acceso al mismo objeto de exclusin mutua. Aunque se puede utilizar una exclusin mutua para la sincronizacin de subprocesos dentro de un proceso, normalmente es preferible

utilizar Monitor porque los monitores se disearon especficamente para .NET Framework y, por tanto, hacen un mejor uso de los recursos. Por el contrario, la clase Mutex es un contenedor para una construccin de Win32. Aunque es ms eficaz que un monitor, la exclusin mutua requiere transiciones de interoperabilidad, que utilizan ms recursos del sistema que la clase Monitor.

Para concluir puedo decir que los hilos o threads, son una aplicacin muy til para correr varios procesos a la vez sin tener ningn problema con el programa. Y en C# es muy fcil crearlos con la clase Thread y para manipularlos se usan los mtodos Start, Join, Stop, Resume y entre otros. La forma desordenada de los subprocesos significa que el acceso a recursos como identificadores de archivos, conexiones de red y memoria se debe coordinar. De lo contrario, dos o ms subprocesos podran tener acceso al mismo tiempo al mismo recurso, cada uno desprevenido de las acciones del otro y eso ocasionaran daos imprevisibles en los datos. Para la sincronizacin de subprocesos se puede lograr con miembros de la clase Interlocked.

2012 Microsoft. Msdn. Visual Studio 2005. Recuperado el 21 de Noviembre del 2009, http://msdn.microsoft.com/es-es/library/ms173179(v=vs.80).aspx

Vous aimerez peut-être aussi