Você está na página 1de 16

Marco Besteiro y Miguel Rodrguez Threads o Hilos

1/16
Threads o Hilos
En .NET, cuando se lanza una aplicacin se crea un proceso y dentro de este proceso un
hilo de ejecucin o thread para el mtodo Mai n. Es posible, a la vez que se ejecuta el
mtodo Mai n, que la aplicacin lance internamente nuevos hilos de ejecucin en los que
se ejecute el cdigo de algn mtodo.

Un ejemplo muy actual de utilizacin de threads es una aplicacin de tipo servidor Web,
como el IIS, el Apache Web Server, etc... Un servidor Web es una aplicacin que, al ser
lanzada y creado para ella un proceso y un hilo para el mtodo Mai n, espera a que llegue
una peticin ht t p de un cliente al puerto que escucha (el 80 generalmente) y cuando
llega hace dos cosas:

- Crea un nuevo hilo de ejecucin o thread en el que atiende la peticin.
- Vuelve inmediatamente (en el hilo principal) a escuchar el puerto para atender
nuevas peticiones (mientras se atiende la peticin en el nuevo hilo).

Cada vez que llegue una nueva peticin se repetir el mismo proceso, de modo que se
crearn nuevos hilos, pudiendo haber n hilos en ejecucin simultneamente, uno
principal y n- 1 atendiendo peticiones ht t p de los clientes. Es de rigor puntualizar que
al decir simultneamente se piensa en condiciones ideales (un procesador por hilo),
hablando en tales condiciones de concurrencia real de varios hilos o threads.

En equipos con menos procesadores que hilos o threads lanzados en un momento dado,
se habla de concurrencia aparente, ya que todos los hilos no pueden estar ejecutndose a
la vez. No obstante, no se ha de pensar que tener un solo procesador hace intil lanzar
ms de un hilo o thread simultneamente. Ni mucho menos, el 100% del tiempo de
ejecucin de un hilo no est ocupado el procesador (interaccin con el usuario,
entrada/salida, acceso a memoria), de modo que otro hilo puede aprovechar sus tiempos
muertos. Tambin es cierto que un exceso de threads o hilos resulta negativo, ya que se
puede perder ms tiempo saltando de un thread a otro (a esta operacin se la llama
conmutacin de contexto e implica salvar y recuperar datos y registros de memoria)
que en la ejecucin real.

En Windows, aunque slo se disponga de un procesador, se permite ejecutar varios
hilos simultneamente (concurrencia aparente). Lo que se hace es ofrecer un tiempo
determinado de ejecucin (t i me sl i ce o rodaja de tiempo) a cada hilo (realmente
son milisegundos). Cuando ese tiempo finaliza, Windows retoma el control y se lo cede
a otro thread. De este modo se ofrece al usuario la ilusin de tener varias aplicaciones
en ejecucin simultneamente y tambin se optimiza el uso de los recursos. A este
modo de organizar la ejecucin de varios threads se le llama pr eempt i ve
mul t i t aski ng (multitarea preemptiva)

En realidad, el sistema operativo y cualquier hilo que se lance ya son dos hilos, con lo
cual la ejecucin en Windows es siempre multihilo.

Marco Besteiro y Miguel Rodrguez Threads o Hilos
2/16
La clase Thread.
Esta clase pertenece al namespace Syst em. Thr eadi ng. Para crear un thread slo hay
que crear una instancia de esta clase. Sus mtodos ms importantes son:

- st ar t : lanza el thread a ejecucin.
- suspend: detiene momentneamente la ejecucin del thread.
- r esume: activa el thread suspendido, es decir, lo vuelve a poner en ejecucin.
- abor t : aborta o para de modo inmediato la ejecucin del thread.
- j oi n: detiene el thread donde se invoca hasta que el thread para el que se le
invoca termina.

Las propiedades ms interesantes de la clase Thr ead son:
- Name: permite darle un nombre a un thread que lo distinga del resto.
- Cur r ent Thr ead: contiene una referencia al thread que est actualmente en
ejecucin.

Ejecucin de un thread.
Un thread no es ms que un bloque de cdigo vaco por defecto que es posible lanzar a
ejecucin de modo simultneo a otros threads.

Para que el mtodo st ar t de la clase Thr ead lance un thread que ejecute un cdigo
real, ha de recibir como parmetro una referencia o delegate de tipo Thr eadSt ar t al
punto de entrada del cdigo real. Thr eadSt ar t es un del egat e que se utiliza para
referenciar el punto de entrada de un cdigo que se desea sea el punto de entrada de un
thread. Su definicin es:

publ i c del egat e voi d Thr eadSt ar t ( ) ;

Para ilustrar lo comentado, supngase que se desea crear una aplicacin desde la que se
lance un thread que referencie una funcin que muestre 10 veces un mensaje (Hol a,
soy el t hr ead) en la consola. La aplicacin lanzar tambin la funcin desde el
thread principal de la aplicacin (Mai n).

El cdigo necesario para crearlo es:

usi ng Syst em;
usi ng Syst em. Thr eadi ng;

namespace Thr eads1
{
cl ass Pr uebaThr ead
{
st at i c voi d Mai n( st r i ng[ ] ar gs)
{
Thr ead mi Thr ead = new Thr ead( new Thr eadSt ar t ( Mi Fun) ) ;
mi Thr ead. St ar t ( ) ;
Mi Fun( ) ;
}

publ i c st at i c voi d Mi Fun( )
{
Marco Besteiro y Miguel Rodrguez Threads o Hilos
3/16
Syst em. Consol e. Wr i t eLi ne( " Hol a, soy el t hr ead" ) ;
f or ( i nt i =0; i <=10; i ++)
Syst em. Consol e. Wr i t eLi ne ( " I t er aci n: " + i ) ;
}
}
}

Al llamar a mi Thr ead. St ar t se lanza un nuevo thread con lo que hay dos threads en
ejecucin, el principal y mi Thr ead. Si se ejecuta este ejemplo, el resultado se reproduce
en la figura 10.1:



Figura 10.1


Este resultado provoca una pequea desilusin, ya que parece que ambos threads se
ejecutan de modo secuencial y adems no hay modo de distinguir cul es el principal y
cul el secundario.
Para conseguir un ejemplo ms ilustrativo pueden utilizarse las propiedades Name y
Cur r ent Thr ead. Con la propiedad Name puede darse nombre a ambos threads y con la
propiedad Cur r ent Thr ead puede obtenerse el thread principal para darle nombre. Lo
mejor es verlo con un ejemplo:

usi ng Syst em;
usi ng Syst em. Thr eadi ng;

namespace Thr eads1
{
cl ass Pr uebaThr ead
{
st at i c voi d Mai n( st r i ng[ ] ar gs)
{
/ / Dar un nombr e al t hr ead pr i nci pal par a di st i ngui r l o
/ / del secundar i o cuando se muest r e su i nf or maci n por
/ / consol a
Thr ead. Cur r ent Thr ead. Name = " Pr i nci pal " ;
Thr ead mi Thr ead = new Thr ead( new Thr eadSt ar t ( Mi Fun) ) ;
mi Thr ead. Name = " Thr eadMi Fun" ;
mi Thr ead. St ar t ( ) ;
Marco Besteiro y Miguel Rodrguez Threads o Hilos
4/16
Mi Fun( ) ;
}

publ i c st at i c voi d Mi Fun( )
{
Syst em. Consol e. Wr i t eLi ne( " Hol a, soy el t hr ead" ) ;
f or ( i nt i =0; i <=10; i ++)
Syst em. Consol e. Wr i t eLi ne
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) + " - -
I t er aci n: " + i ) ;
}
}
}

El resultado en este caso ser:




Figura 10.2


Ahora, por lo menos, se sabe en cada lnea qu thread la ha generado. Lo curioso es que
el orden es el inverso del esperado y sigue siendo secuencial.

El orden de comienzo de los threads no tiene porqu ser el mismo en el que se lancen
las funciones, depende de la prioridad de los threads y otros factores. El que sea
secuencial se debe a que la ejecucin lleva poco tiempo. Si se inicializa el contador i
de la funcin Mi Fun a 1000, en lugar de a 10 podr comprobarse cmo la ejecucin no
es secuencial, sino que cada hilo recibe una rodaja de tiempo (figura 10.3).

Marco Besteiro y Miguel Rodrguez Threads o Hilos
5/16


Figura 10.3

Parada y activacin de un Thread.
Una vez un thread ha sido lanzado puede ser suspendido (suspend), reactivado
(r esume) o abortado (abor t ).

suspend y r esume: suspend suspende la ejecucin de un thread momentneamente.
Un thread suspendido puede ser reactivado llamando a r esume. Si no se utilizan
bien pueden causar situaciones de bloqueo no recuperables.
abor t : lanza una excepcin Thr eadAbor t Except i on en el thread. El tratamiento
por defecto para esta excepcin es finalizar la ejecucin del thread. No obstante si se
captura la excepcin, se puede programar el tratamiento que se desee.

Supngase que se desea crear una aplicacin que lance dos funciones (Mi Fun y Mi Fun2)
en sendos threads. Mi Fun muestra nmeros del 1 al 10 y Mi Fun2 letras de la a a la z.
Se desea tambin que se ejecute primero Mi Fun pero slo hasta mostrar los 5 primeros
nmeros y quedar suspendida, tras lo cual debe ejecutarse Mi Fun2 hasta acabar y luego
debe reactivar a Mi Fun. El cdigo para hacer esto es (se resaltan en negrita los puntos
clave):

usi ng Syst em;
usi ng Syst em. Thr eadi ng;

namespace Thr eads2
{
cl ass Par ar Act i var Thr ead
{
st at i c Thr ead mi Thr ead = nul l ;
st at i c voi d Mai n( st r i ng[ ] ar gs)
{
mi Thr ead = new Thr ead( new Thr eadSt ar t ( Mi Fun) ) ;
mi Thr ead. Name = " Thr eadMi Fun" ;

Thr ead mi Thr ead2 = new Thr ead( new
Thr eadSt ar t ( Mi Fun2) ) ;
Marco Besteiro y Miguel Rodrguez Threads o Hilos
6/16
mi Thr ead2. Name = " Thr eadMi Fun2" ;

mi Thr ead. St ar t ( ) ;
mi Thr ead2. St ar t ( ) ;
}

publ i c st at i c voi d Mi Fun( )
{
Syst em. Consol e. Wr i t eLi ne( " Hol a, soy el t hr ead " +
Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
f or ( i nt i =0; i <=10; i ++)
{
Syst em. Consol e. Wr i t eLi ne
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) + " - -
I t er aci n: " + i ) ;
if (i==5)
{
Thread.CurrentThread.Suspend();
}
}
}

publ i c st at i c voi d Mi Fun2( )
{
Syst em. Consol e. Wr i t eLi ne( " Hol a, soy el t hr ead " +
Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
f or ( char c=' a' ; c<=' z' ; c++)
{
Syst em. Consol e. Wr i t eLi ne
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) + " - -
I t er aci n: " + c) ;
}
miThread.Resume();
}
}
}

El resultado ser:



Figura 10.4

Marco Besteiro y Miguel Rodrguez Threads o Hilos
7/16
Join.
El mtodo J oi n pertenece a la clase Thr ead y lo que hace es detener la ejecucin del
thread donde se invoca hasta que el thread para el que se invoca termina.
Por ejemplo:

usi ng Syst em;
usi ng Syst em. Thr eadi ng;

namespace Thr eads1
{
cl ass Pr uebaThr ead
{
st at i c voi d Mai n( st r i ng[ ] ar gs)
{
/ / Dar un nombr e al t hr ead pr i nci pal par a di st i ngui r l o
/ / del secundar i o cuando se muest r e su i nf or maci n por
/ / consol a
Thr ead. Cur r ent Thr ead. Name = " Pr i nci pal " ;
Thr ead mi Thr ead = new Thr ead( new Thr eadSt ar t ( Mi Fun) ) ;
mi Thr ead. Name = " Thr eadMi Fun" ;
mi Thr ead. St ar t ( ) ;
miThread.Join();
Mi Fun( ) ;
}

publ i c st at i c voi d Mi Fun( )



La llamada a J oi n del ejemplo hace que el hilo principal detenga su ejecucin hasta que
acabe mi Thr ead. El efecto de esta llamada a J oi n es que la llamada a Mi Fun desde el
hilo principal no se ejecute hasta que acabe mi Thr ead, con lo que en la consola se ver
primero la salida correspondiente a mi Thr ead. En realidad, este ejemplo no tiene
sentido, ya que fuerza a una ejecucin secuencial de los threads, independientemente de
que el sistema pueda o no ejecutarlos concurrentemente.

Para observar un ejemplo con sentido se propone un cambio en la clase
Par ar Act i var Thr ead. El cambio consiste en mostrar un mensaje cuando acaben de
ejecutarse los threads mi Thr ead y mi Thr ead2, indicando el mensaje Han f i nal i zado
l os t hr eads. En un principio, el cdigo lgico es:

usi ng Syst em;
usi ng Syst em. Thr eadi ng;

namespace Thr eads2
{
/ / / <summar y>
/ / / Summar y descr i pt i on f or Cl ass1.
/ / / </ summar y>
cl ass Par ar Act i var Thr ead
{
st at i c Thr ead mi Thr ead = nul l ;
st at i c voi d Mai n( st r i ng[ ] ar gs)
{
/ / Dar un nombr e al t hr ead pr i nci pal par a di st i ngui r l o
/ / del secundar i o
/ / cuando se muest r e su i nf or maci n por consol a
Marco Besteiro y Miguel Rodrguez Threads o Hilos
8/16
/ / Thr ead. Cur r ent Thr ead. Name = " Pr i nci pal " ;
mi Thr ead = new Thr ead( new Thr eadSt ar t ( Mi Fun) ) ;
mi Thr ead. Name = " Thr eadMi Fun" ;
Thr ead mi Thr ead2 = new Thr ead( new
Thr eadSt ar t ( Mi Fun2) ) ;
mi Thr ead2. Name = " Thr eadMi Fun2" ;

mi Thr ead. St ar t ( ) ;
mi Thr ead2. St ar t ( ) ;
Console.WriteLine("Han finalizado los threads");
}
. . .
. . .

Si se ejecuta la aplicacin con este cambio, el resultado ser:



Figura 10.5

Este no es el mensaje esperado ya que el mensaje Han f i nal i zado l os t hr eads se
muestra antes de que comiencen.

A pesar de no ser el mensaje esperado tiene mucho sentido. Si se observa de nuevo el
cdigo, teniendo en cuenta que se ejecuta en el Mai n, es decir, en el hilo principal:

mi Thr ead. St ar t ( ) ;
mi Thr ead2. St ar t ( ) ;
Console.WriteLine("Han finalizado los threads");

Se puede deducir que el hilo principal lanza mi Thr ead, despus lanza mi Thr ead2 e
inmediatamente despus sigue en ejecucin, en la instruccin Consol e. Wr i t eLi ne.
Como el hilo principal tiene la ms alta de las prioridades lo primero que se ejecuta es
Consol e. Wr i t eLi ne .

Llamando al mtodo J oi n desde el hilo principal, para los hilos secundarios, se puede
corregir esta situacin.

mi Thr ead. St ar t ( ) ;
Marco Besteiro y Miguel Rodrguez Threads o Hilos
9/16
mi Thr ead2. St ar t ( ) ;
miThread.Join();
miThread2.Join();
Consol e. Wr i t eLi ne( " Han f i nal i zado l os t hr eads" ) ;

En este cdigo se indica que el thread principal se ha de detener hasta que mi Thr ead
termine y hasta que mi Thr ead2 termine (poner un solo J oi n esperara por un solo
thread y continuara, aqu se desea continuar slo cuando los dos threads hayan
acabado). Si se ejecuta ahora la aplicacin, el resultado ser esperado (figura 10.6):



Figura 10.6

El mtodo J oi n es realmente un mtodo de sincronizacin (la sincronizacin se explica
ms adelante) y consiste en que quien lo invoca (en este caso el hilo principal) ejecuta el
mtodo Wai t (ver la clase Moni t or , ms adelante) , detenindose, y el thread sobre el
que es invocado ejecuta el mtodo Pul se (ver la clase Moni t or , ms adelante) cuando
termina, permitiendo a quien se detuvo mediante un Wai t reanudar su ejecucin.

De lo comentado se deduce un peligro. Qu sucede si el thread para el que se ha
llamado a J oi n no termina o no llega nunca a llamar a Pul se?. En tal caso, el hilo desde
el que se ha llamado a J oi n (desde el que se ha hecho la Wai t ) queda bloqueado
indefinidamente. Para solucionar este problema se permite que cuando se llama a J oi n
se fije un tiempo mximo de espera. Para ello se ha de pasar un parmetro de tipo entero
a J oi n indicando el nmero de milisegundos que como mximo se puede esperar (J oi n
admite tambin un parmetro de la clase Ti meSpan). Por ejemplo:

mi Thr ead. St ar t ( ) ;
mi Thr ead2. St ar t ( ) ;
miThread.Join(100);
miThread2.Join(100);
Consol e. Wr i t eLi ne( " Han f i nal i zado l os t hr eads" ) ;

El tiempo mximo que espera el thread principal por ambos threads secundarios es de
100 milisegundos.
Marco Besteiro y Miguel Rodrguez Threads o Hilos
10/16
Prioridades.
Cuando se lanzan varios threads para que se ejecuten simultneamente, se les asignan
las rodajas de tiempo en funcin del valor que tenga su propiedad Pr i or i t y.

La propiedad Pr i or i t y es una propiedad pblica de la clase Thr ead (no es st at i c).
Realmente es una enumeracin del tipo Thr eadPr i or i t y, cuyos posibles valores son:

Valor Descripcin
AboveNor mal
El t hr ead tiene la prioridad por encima de la normal.
Bel owNor mal
El t hr ead tiene la prioridad por debajo de la normal.
Hi ghest
El t hr ead tiene la prioridad ms alta.
Lowest
El t hr ead tiene la prioridad ms baja.
Nor mal
El t hr ead tiene la prioridad normal.

Tabla 10.1

Un ejemplo de la influencia que tiene la prioridad de un thread se puede observar en la
aplicacin en que se lanzaba Mi Fun dos veces, una en un thread secundario y otra en el
principal. A pesar de lanzar primero el secundario y luego el principal se ejecutaba
primero el principal y despus el secundario (ya que el principal tiene la prioridad, por
defecto, ms alta que el secundario).

Pueden hacerse pruebas con las prioridades de los threads en el ejemplo anterior, tanto
para consultarlas como para cambiarlas, observando el resultado. Por ejemplo, si se
desea que la prioridad del segundo thread (Mi Thr ead2) est por encima de lo normal, se
ha de hacer:

mi Thr ead2. Pr i or i t y = Thr eadPr i or i t y. AboveNor mal ;

Sincronizacin.
La sincronizacin de threads consiste en asegurar que distintos threads acceden de modo
coordinado a recursos compartidos.
El caso ms sencillo es la sincronizacin del uso del procesador, lo cual se lleva a cabo
por el sistema operativo mediante la multitarea preemptiva (rodajas de tiempo).
Pero existen otras situaciones en las que la sincronizacin es necesaria y no la hace por
defecto el sistema operativo.

lock.
La sentencia l ock bloquea el acceso a un bloque de cdigo, asegurando que slo el
thread que lo ha bloqueado tiene acceso a tal bloque.

Supngase la siguiente clase:

cl ass Cuent a
{
i nt sal do;
Marco Besteiro y Miguel Rodrguez Threads o Hilos
11/16
Randomnum= new Random( ) ;

i nt er nal Cuent a( i nt sal do_i ni ci al )
{
sal do = sal do_i ni ci al ;
}

i nt Real i zar _Tr ansacci on( i nt cant i dad)
{
i f ( sal do >= cant i dad)
{
/ / El mt odo st at i c sl eep det i ene l a ej ecuci n del
/ / t hr ead dur ant e 5 mseg.
Thr ead. Sl eep( 5) ;
sal do = sal do - cant i dad;
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t eLi ne( " - - Sal do= " +
sal do. ToSt r i ng( ) ) ;
r et ur n sal do;
}
el se
{
/ / si el bal ance es menor que l a
/ / cant i dad que se desea r et i r ar
/ / se deni ega l a t r ansacci n
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t eLi ne( " - - Tr ansacci n denegada. " ) ;
Syst em. Consol e. Wr i t eLi ne( " - - El sal do ser a= " +
( sal do - cant i dad) ) ;
r et ur n 0;
}
}

i nt er nal voi d Real i zar _Tr ansacci ones( )
{
f or ( i nt i = 0; i < 5; i ++)
{
/ / devuel ve un nmer o al eat or i o ent r e - 50 y 100
Real i zar _Tr ansacci on( num. Next ( - 50, 100) ) ;
}
}
}

El mtodo ms importante es Real i zar _Tr ansacci on que recibe como parmetro un
valor cant i dad y en caso de que sea menor que el sal do, se lo resta. Si la cantidad es
mayor que el sal do, no se hace la resta y se deniega la transaccin.

El mtodo Real i zar _Tr ansacci ones se ha creado para poder simular varias
transacciones sobre la cuenta llamndolo slo una vez.

A continuacin se muestra una clase Pr ueba_Cuent a que crea una instancia de la clase
Cuent a y diez threads que realizan transacciones sobre la misma mediante el mtodo
Real i zar _Tr ansacci ones.

cl ass Pr ueba_Cuent a
{
Marco Besteiro y Miguel Rodrguez Threads o Hilos
12/16
st at i c i nt er nal Thr ead[ ] t hr eads = new Thr ead[ 10] ;

publ i c st at i c voi d Mai n( )
{
Cuent a c1 = new Cuent a ( 0) ;
f or ( i nt i = 0; i < 10; i ++)
{
Thr ead t = new Thr ead( new
Thr eadSt ar t ( c1. Real i zar _Tr ansacci ones) ) ;
t hr eads[ i ] = t ;
t hr eads[ i ] . Name = " Thr ead- " + i . ToSt r i ng( ) ;
}
f or ( i nt i = 0; i < 10; i ++)
{
t hr eads[ i ] . St ar t ( ) ;
}
}
}

Si se crea una aplicacin con las clases anteriores y se ejecuta, el resultado es el de la
figura 10.7:



Figura 10.7


Pero parece no tener sentido, el cdigo no permite que la cuenta llegue a saldo negativo
y no se han denegado todas las transacciones que lo dejan negativo.

La realidad es que s tiene sentido. El cdigo de la funcin Real i zar _Tr ansacci on es
invocado por todos los threads en ejecucin y por tanto compartido por todos ellos. Si
en el momento en que un thread ha comprobado que el saldo (100 por ejemplo) es
mayor que la cantidad pedida (80 por ejemplo), justo antes de que haga la resta, se le
quita el control y se le da a otro, el nuevo thread creer que el saldo es el que hay (100)
y si se le pide que reste 50 lo har, quedando el saldo a 50. Cuando el thread
interrumpido recupere el control, realizar la resta en la que se lo quitaron, restando 80
y dejando el saldo a 30.

Marco Besteiro y Miguel Rodrguez Threads o Hilos
13/16
Para resolver este problema, ningn thread debiera perder el control del procesador
mientras ejecuta el mtodo Real i zar _Tr ansacci on, es decir, mientras un thread
ejecuta el mtodo Real i zar _Tr ansacci on ste debe estar bloqueado a los dems
threads, que esperarn para utilizarlo a que acabe el thread que lo bloquea, momento en
el que ser desbloqueado.

Esto que explicado es tan largo, se reduce a introducir el cdigo del mtodo
Real i zar _Tr ansacci on en una sentencia l ock.

i nt Real i zar _Tr ansacci on( i nt cant i dad)
{
/ / coment ar est e mt odo par a ver el pr obl ema de no usar l o
l ock( t hi s)
{
i f ( sal do >= cant i dad)
{
Thr ead. Sl eep( 5) ;
sal do = sal do - cant i dad;
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t eLi ne( " - - Sal do= " +
sal do. ToSt r i ng( ) ) ;
r et ur n sal do;
}

el se
{
/ / si el bal ance es menor que l a
/ / cant i dad que se desea r et i r ar
/ / se deni ega l a t r ansacci n
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t e ( " - - Tr ansacci n denegada. " ) ;
Syst em. Consol e. Wr i t eLi ne ( " - - El sal do ser a= " +
( sal do - cant i dad) ) ;
r et ur n 0;
}
}
}

El resultado de ejecutar la aplicacin con los nuevos cambios se reproduce en la figura
10.8:

Marco Besteiro y Miguel Rodrguez Threads o Hilos
14/16


Figura 10.8


Como puede observarse, la cuenta no se queda con saldo negativo.

System.Threading.Monitor
La clase Moni t or implementa el concepto de monitor de sincronizacin. Del mismo
modo que existe esta clase existen otras como Mut ex, etc...

No es el objetivo aqu profundizar en los conceptos del multihilo y de la sincronizacin.
No obstante s se comentar algn aspecto de la clase Moni t or , ya que la sentencia
l ock equivale a utilizar un Moni t or llamando a sus mtodos Ent er (al comienzo del
bloque) y Exi t (al final del bloque).

Los mtodos ms importantes de la clase monitor son:
- Ent er : bloquea el bloque de cdigo al que precede.
- Tr yEnt er : es similar a Ent er pero no bloquea o produce slo un bloqueo
temporal.
- Exi t : libera el bloque.
- Wai t : Detiene al thread que lo llama (muy importante, no bloquea el cdigo a
otros threads sino que detiene al thread llamador) dejndolo en espera de que
otro thread le notifique que puede seguir mediante el mtodo Pul se. Tambin
libera el bloqueo que haya hecho ese thread si es que lo hay.
- Pul se: notifica a un thread en la cola de espera (Wai t ) que puede continuar.
- Pul seAl l : realiza la notificacin a todos los threads de la cola de espera.

A continuacin, como ejemplo, se sustituye en el ejercicio anterior l ock por las
correpondientes llamadas a Ent er y Exi t en la clase Moni t or .

i nt Real i zar _Tr ansacci on( i nt cant i dad)
{
/ / coment ar est e mt odo par a ver el pr obl ema de no usar l o
Marco Besteiro y Miguel Rodrguez Threads o Hilos
15/16
/ / l ock( t hi s)
Monitor.Enter(this);
{
i f ( sal do >= cant i dad)
{
Thr ead. Sl eep( 5) ;
sal do = sal do - cant i dad;
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t eLi ne ( " - - Sal do= " +
sal do. ToSt r i ng( ) ) ;
Monitor.Exit(this);
r et ur n sal do;
}
el se
{
/ / si el bal ance es menor que l a
/ / cant i dad que se desea r et i r ar
/ / se deni ega l a t r ansacci n
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t e ( " - - Tr ansacci n denegada. " ) ;
Syst em. Consol e. Wr i t eLi ne ( " - - El sal do ser a= " +
( sal do - cant i dad) ) ;
Monitor.Exit(this);
r et ur n 0;
}
}
}

Se puede observar que las llamadas a Exi t han de ser antes de r et ur n o no se
ejecutarn.

Una apreciacin que puede hacerse es que al poder decidir cundo bloquear y cundo
desbloquear podra afinarse an ms, realizando el desbloqueo cuando ya se haya hecho
la resta del saldo y la cantidad.

i nt Real i zar _Tr ansacci on( i nt cant i dad)
{
/ / coment ar est e mt odo par a ver el pr obl ema de no usar l o
/ / l ock( t hi s)
Monitor.Enter(this);
{
i f ( sal do >= cant i dad)
{
Thr ead. Sl eep( 5) ;
sal do = sal do - cant i dad;
Monitor.Exit(this);
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t eLi ne ( " - - Sal do= " +
sal do. ToSt r i ng( ) ) ;
r et ur n sal do;
}
el se
{
/ / si el bal ance es menor que l a
/ / cant i dad que se desea r et i r ar
/ / se deni ega l a t r ansacci n
Monitor.Exit(this);
Marco Besteiro y Miguel Rodrguez Threads o Hilos
16/16
Syst em. Consol e. Wr i t e
( Thr ead. Cur r ent Thr ead. Name. ToSt r i ng( ) ) ;
Syst em. Consol e. Wr i t e ( " - - Tr ansacci n
denegada. " ) ;
Syst em. Consol e. Wr i t eLi ne ( " - - El sal do ser a= "
+ ( sal do - cant i dad) ) ;
r et ur n 0;
}
}
}

De este modo se optimiza el tiempo de ejecucin, al no perder tiempo en bloqueos
innecesarios.

Es muy importante utilizar bien la clase Moni t or y nunca dejar un bloqueo sin
desbloqueo posterior, ya que se puede llegar a una situacin de deadl ock o abrazo
mortal, quedando la aplicacin completa bloqueada sin posibilidad de continuar.
Para probar esto basta con eliminar la llamada a Moni t or . Exi t ( t hi s) de cualquiera de
las ramas del i f .