Sunteți pe pagina 1din 41

1.

INTRODUCCIN
Casi todos los moderno sistema operativo o entorno de programacin proporciona soporte para
programacin concurrente. El mecanismo ms popular para esto es alguna provisin para permitir
que a mltiples ligero "hilos" dentro de un espacio de direccin nica, utilizados desde dentro de
un solo programa.
Programacin con hilos introduce nuevas dificultades incluso para programadores
experimentados. Programacin concurrente tiene tcnicas y errores que no se producen en la
programacin secuencial. Muchas de las tcnicas son evidentes, pero algunas son obvias slo en
retrospectiva. Algunos de los escollos son cmodas (por ejemplo, estancamiento es una especie de
insecto agradable detiene su programa con toda la evidencia intacta), pero algunos toman la
forma de sanciones rendimiento insidioso.
El propsito de este papel es para darle una introduccin a las tcnicas de programacin que
funcionan bien con hilos y para advertirle sobre tcnicas o interacciones que funcionan mal. Debe
proporcionar el experimentado programador secuencial con suficientes pistas para poder crear un
programa de multithreaded substancial que trabaja correctamente, eficiente y con un mnimo de
sorpresas.
Este documento es una revisin de uno que he publicado originalmente en 1989 [2]. Con los
aos ese papel se ha utilizado extensivamente en ensear a los estudiantes cmo programar con
hilos. Pero mucho ha cambiado desde hace 14 aos, tanto en diseo del lenguaje y el diseo de
hardware de computadora. Espero que esta revisin, mientras que presenta esencialmente las
mismas ideas que el documento anterior, les har ms accesible y ms til para un pblico
contemporneo.
Un "hilo" es un concepto sencillo: un nico flujo secuencial de control. En un lenguaje high
level normalmente programar un hilo usando llamadas de procedimiento o mtodo, donde las
llamadas seguir la disciplina tradicional pila. Dentro de un nico subproceso, hay en todo momento
un nico punto de ejecucin. El programador necesita aprender nada nuevo para usar un nico
subproceso.
Tener "varios subprocesos" en un medio de programa que en cualquier momento el programa
tiene mltiples puntos de ejecucin, uno en cada uno de sus hilos. El programador puede ver sobre
todo los hilos como ejecutar simultneamente, como si la computadora fueron dotada con tantos
procesadores como hay hilos. El programador es necesario para decidir cuando y donde para crear
mltiples hilos, o a aceptar tales decisiones hecho para l por los implementadores de paquetes de
bibliotecas existentes o sistemas de tiempo de ejecucin. Adems, el programador de vez en cuando
debe ser consciente de que en la computadora no puede de hecho ejecutar todos sus hilos
simultneamente.
Tener que los hilos ejecutan dentro de un "espacio de direccin nica" significa que el
direccionamiento de computacin est configurado para permitir las roscas a leer y escribir las
mismas localizaciones de memoria. En un lenguaje tradicional highlevel, esto corresponde
generalmente al hecho de que las variables (globales) offstack son compartidas entre todos los
subprocesos del programa. En un lenguaje objectoriented como C# o Java, las variables estticas de
una clase son compartidas entre todos los hilos, como son las variables de instancia de cualquier
objeto que los hilos comparten.* Cada subproceso ejecuta en una pila de llamadas independiente
con sus propias variables locales independientes. El programador es
* Hay un mecanismo en C# (y en Java) para hacer esttica campos threadspecific y no comparte, pero voy
a ignorar esa caracterstica en este papel. 2 . Introduccin a la programacin con C# hilos

responsable de utilizar los mecanismos de sincronizacin de la instalacin del hilo de rosca


para asegurarse de que la memoria compartida es accesible de una manera que le dar la respuesta
correcta.*

* ElCLR(Common Language Runtime) utilizado por C# Aplicaciones introduce el concepto adicional de


"Dominio de aplicacin", que permite mltiples programas para ejecutar en un espacio de direcciones de
hardware nico, pero que no afecta a cmo su programa utiliza subprocesos.

Instalaciones de hilo siempre se anuncian como "ligero". Esto significa que primitivas de
sincronizacin, existencia, destruccin y creacin de hilo son lo suficientemente baratos para que el
programador los utilizar para todas las necesidades de su concurrencia.
Por favor tenga en cuenta que te presento con una coleccin de tcnicas selectiva, sesgada e
idiosincrsica. Selectiva, porque una encuesta exhaustiva sera demasiado agotador para servir
como una introduccin discutirn slo las primitivas ms importantes de hilo, omitiendo las
caractersticas tales como perthread informacin de contexto o el acceso a otros mecanismos como
exclusiones mutuas del ncleo NT o eventos. Parcial, porque presento ejemplos, problemas y
soluciones en el contexto de un conjunto particular de opciones de cmo disear una instalacin de
hilos las decisiones tomadas en la programacin de C# lenguaje y su sistema compatible de
tiempo de ejecucin. Idiosincrsicos, porque las tcnicas presentadas aqu derivan de mi
experiencia personal de programacin con hilos en los ltimos veinticinco aos (desde 1978) no
he intentado representar a colegas que tengan opiniones diferentes sobre los cuales las tcnicas de
programacin son "buenas" o "importante". Sin embargo, creo que la comprensin de las ideas
presentadas aqu servir como una base slida para la programacin con hilos concurrentes.
A lo largo del papel uso ejemplos escritos en C# [14]. Estos deben ser fcilmente comprensibles
por cualquiera que est familiarizado con los idiomas modernos objectoriented, incluyendo Java
[7]. Donde Java difiere significativamente de C#, intento sealar esto. Los ejemplos sirven para
ilustrar puntos de sincronizacin y concurrencia no trate de utilizar estos algoritmos reales en
programas reales.
Hilos de rosca no son una herramienta para la descomposicin paralela automtica, donde un
compilador tomar un programa visiblemente secuencial y generar el cdigo objeto para utilizar
varios procesadores. Es un arte completamente diferente, no se que voy a comentar aqu.

2. POR QU USAR CONCURRENCIA?


La vida sera ms simple si no necesitas usar concurrencia. Pero hay una gran variedad de fuerzas
que empujan hacia su uso. La ms evidente es el uso de multiprocessors. Con estas mquinas, hay
varios puntos simultneos de ejecucin y roscas son una herramienta atractiva para permitir que un
programa aprovechar el hardware disponible. La alternativa, con los sistemas operativos ms
convencionales, es para configurar su programa como mltiples procesos separados, en espacios de
direcciones separadas. Esto tiende a ser costoso configurar, y los costos de comunicacin entre
espacios de direcciones a menudo son altos, incluso en presencia de segmentos compartidos.
Mediante el uso de una instalacin de multithreading ligero, el programador puede utilizar los
procesadores barato. Esto parece que funciona bien en sistemas teniendo unos 10 procesadores, en
lugar de 1000 procesadores. Introduccin a la programacin con C# hilos . 3

Es una segunda rea donde hilos son tiles en la conduccin lentos dispositivos tales como
discos, redes, terminales e impresoras. En estos casos un programa eficiente debera estar haciendo
algn otro trabajo til mientras se espera para que el dispositivo producir su prximo evento tales
como (la realizacin de una transferencia de disco) o la recepcin de un paquete de la red. Como
veremos ms adelante, esto puede ser programado fcilmente con hilos adoptando una actitud que
dispositivo peticiones son todos secuenciales (es decir, suspensin la ejecucin del subproceso
invocando hasta que se complete la solicitud), y que el programa mientras tanto otros trabajos en
otros subprocesos. Exactamente las mismas observaciones se aplican a mayor niveles lento las
peticiones, tales como realizar una llamada RPC a un servidor de red.
Una tercera fuente de concurrencia es usuarios humanos. Cuando el programa est realizando
una tarea larga para el usuario, el programa todava deberan responder: deben pintar ventanas
expuestas, barras de desplazamiento deben desplazarse de sus contenidos y cancelar botones deben
clic y aplicar la cancelacin. Las roscas son una forma conveniente de programacin: la larga tarea
se ejecuta en un subproceso independiente del hilo de procesamiento de eventos entrantes GUI; Si
volver a pintar un dibujo complejo llevar mucho tiempo, tendr que ser tambin en un subproceso
independiente. En una seccin 6, discutir algunas tcnicas para implementar esta.
Una fuente final de simultaneidad aparece al construir un sistema distribuido. Aqu nos
encontramos con frecuencia los servidores de red compartida (como un servidor web, una base de
datos o un servidor de impresin de cola), donde el servidor est dispuesto a solicitudes de servicio
de mltiples clientes. Uso de mltiples hilos permite al servidor controlar las solicitudes de los
clientes en paralelo, en lugar de los serializar artificialmente (o crear un proceso de servidor por el
cliente, a un gran coste).
A veces puede deliberadamente agregar concurrencia a su programa para reducir la latencia de
las operaciones (el tiempo transcurrido entre llamar a un mtodo y el mtodo devuelve). A
menudo, algunos de los trabajos efectuados por una llamada al mtodo pueden ser diferidas, ya
que no afecta el resultado de la convocatoria. Por ejemplo, al agregar o eliminar algo en un rbol
balanceado podras felices por volver a la la llamada antes de rebalancing el rbol. Con hilos puede
lograr esto fcilmente: hacer el rebalancing en un subproceso independiente. Si el subproceso
independiente est programado a una prioridad ms baja, la obra puede hacerse en un momento
cuando est menos ocupado (por ejemplo, cuando se espera de entrada del usuario). Adicin de
roscas para diferir el trabajo es una tcnica poderosa, incluso en un uniprocessor. Incluso si se hace
el mismo trabajo total, reduciendo la latencia puede mejorar la capacidad de respuesta de su
programa y la felicidad de sus usuarios.

3. EL DISEO DE UNA INSTALACIN DE HILO


No podemos hablar de cmo programar con hilos hasta que coincidimos en los primitivos
proporcionados por un centro de multithreading. Los diversos sistemas que soportan hilos ofrecen
servicios muy similares, pero hay mucha diversidad en los detalles. En general, hay cuatro
mecanismos principales: creacin del hilo de rosca, exclusin mutua, esperando acontecimientos y
algn arreglo para conseguir un hilo de un largo plazo no deseados esperar. Para hacer los debates
en este concreto de papel, se basan en la instalacin de hilo de C#: el " System.Threading"namespace
adems el C# "bloqueo" declaracin.4 . Introduccin a la programacin con C# hilos
* Un "delegado" de C# es slo un objeto construido a partir de un objeto y uno de sus mtodos. En Java podra
en cambio explcitamente definir y crear una instancia de una clase adecuada.
Cuando miras el "System.Threading"namespace, ser (o debe) sentirse intimidado por la gama de

opciones frente a usted:"Monitor"o"Mutex; Espera"o"AutoResetEvent";"Interrumpir"o"Abortar "?

Afortunadamente, hay una respuesta simple: usar el "cerradura" declaracin, el "Monitor" clase y el
"interrumpir" mtodo. Esas son las caractersticas que utilizar para la mayor parte del resto del
documento. Por ahora, usted debe ignorar el resto del "System.Threading", aunque yo a dibujarla
para que la seccin 9.
a lo largo del papel, los ejemplos se supone que estn dentro del mbito del " usando Sistema
de ; usandoSystem.Threading;"
3.1. creacin de hilo
En C# se crea un subproceso mediante la creacin de un objeto de tipo " Hilo de rosca", dando a su
constructor un"ThreadStart"delegar*y el nuevo subproceso "Inicio"mtodo de. El nuevo subproceso
comienza ejecucin asincrnica con una invocacin de mtodo del delegado. Cuando el mtodo
devuelve, muere el hilo. Tambin puede llamar a la " nete" mtodo de un hilo: esto hace que el
subproceso de llamada esperar hasta que termine el subproceso dado. Crear y poner en un
subproceso llama a menudo "que se bifurcan".
Por ejemplo, el siguiente fragmento de programa ejecuta las llamadas al mtodo
"foo.A()"y"foo.B()"en paralelo y termina slo cuando han completado las llamadas de mtodo. Por
supuesto, el mtodo "A"bien podra acceder a los campos de"foo".
Hilo t = nuevo hilo (nuevo ThreadStart (foo.A)); t.Start(); foo.B(); t.Join();

En la prctica, probablemente no usars "nete"mucho. Ms horquilla hilos hilos daimonion


permanente, sin resultados o comunican sus resultados por algn arreglo de sincronizacin que no
sean de "Unir". Est bien para horquilla un hilo, pero nunca tienen una llamada correspondiente de
"unirse a".
3.2. mutua exclusin
La forma ms sencilla que interactan de hilos es a travs del acceso a memoria compartida. En un
lenguaje objectoriented, esto se expresa generalmente como el acceso a las variables que son los
campos estticos de una clase, o campos de instancia de un objeto compartido. Desde hilos
funcionan en paralelo, el programador debe organizar explcitamente evitar los errores que se
presentan cuando ms de un hilo es acceder a las variables compartidas. La herramienta ms
simple para hacer esto es un hombre primitivo que ofrece exclusin mutua (a veces llamada
secciones crticas), especificando para una regin particular del cdigo que slo un subproceso
puede ejecutar all en cualquier momento. En el diseo de C#, esto se logra con la clase " Monitor"y de
la lengua"bloqueo" declaracin:
cerradura Introduccin
declaracin5

a la programacin con C# hilos .

(expresin) incrustado-

El argumento de la " cerradura "declaracin puede ser cualquier objeto: en C# cada objeto
inherentemente implementa un bloqueo de exclusin mutua. En cualquier momento, un objeto o
"bloqueado" o "desbloqueado", inicialmente abierto. El " cerradura" declaracin bloquea el objeto
indicado, ejecuta las sentencias contenidas y luego abre el objeto. Un hilo de ejecucin dentro de la
"cerradura" declaracin se dice que "espera" bloqueo del objeto dado. Si otro subproceso intenta
bloquear el objeto cuando ya est cerrada, el segundo subproceso se bloquee (en cola en la
cerradura del objeto) hasta que el objeto est desbloqueado.
El uso ms comn de la " cerradura "declaracin es proteger los campos de instancia de un
objeto mediante el bloqueo de ese objeto cada vez que el programa est accediendo a los campos.
Por ejemplo, el siguiente fragmento de programa arregla que slo un hilo a la vez puede estar
ejecutando el par de instrucciones de asignacin en la "SetKV" mtodo.
clase KV { cadena k, v; public voidSetKV (nkstring , string nv) { cerradura (este) { este.k =
nk; este.v = nv; } } }

Sin embargo, existen otros patrones para elegir la cerradura del objeto que protege a las variables
que. En general, que lograr la exclusin mutua en un conjunto de variables asocindolos
(mentalmente) con un objeto determinado. Luego escribes tu programa que accede a las variables
slo desde un subproceso que tiene cerradura del objeto (es decir, desde un subproceso ejecutando
dentro de un "cerradura" declaracin que bloquean el objeto). Esta es la base de la nocin de
monitores, descrita por primera vez por Tony Hoare [9]. El lenguaje C# y su ejecucin no hacen
restricciones en su eleccin de qu objeto para bloquear, pero para conservar la cordura debe elegir
obvia. Cuando las variables son campos de instancia de un objeto, ese objeto es la obvia para la
cerradura (al igual que en el "SetKV" mtodo, arriba. Cuando las variables son campos estticos de
una clase, un objeto conveniente es el proporcionado por el runtime de C# para representar el tipo
de la clase. Por ejemplo, en el siguiente fragmento de la " KV"el campo esttico de la
clase"cabeza"est protegido por el objeto"typeof(KV)". El "cerradura" declaracin dentro de la
"AddToList" mtodo de instancia proporciona exclusin mutua para agregar un " KV"objeto de la lista
enlazada cuya cabeza es"cabeza": nico hilo en un momento puede estar ejecutando las
instrucciones que utilizan "cabeza". En este cdigo al campo de instancia "prximo"tambin est
protegido por"typeof(KV)".
esttica Cabeza de KV = null; KV siguiente = null;
public void AddToList() { lock (typeof(KV)) {System.Diagnostics.Debug.Assert (esteprximo ==
null); estasiguiente = cabeza; cabeza = este;}} 6 . Introduccin a la programacin con C# hilos

*Esta garanta de atomicidad evita el problema conocido en la literatura como la carrera "wakeup espera" [18].
Sin embargo, como veremos en la seccin 5.2, es muy difcil no agregar la semntica adicional,
mediante la definicin de su propia clase "condicin Variable"

3.3. esperando una condicin


Puede ver la cerradura de un objeto como un simple mecanismo de programacin de recursos. El
recurso est programado es la memoria compartida accedida dentro de la " cerradura" Declaracin y
la agenda poltica es un subproceso en un momento. Pero a menudo el programador necesita
expresar ms complicado programar polticas. Esto requiere el uso de un mecanismo que permite
que un subproceso se bloquear hasta que una condicin es true. En hilo sistemas predating Java,
este mecanismo era generalmente llamado "variables de estado" y correspondi a un objeto
asignado por separado [4,13]. En Java y C# no hay ningn tipo separado para este mecanismo. En
cambio cada objeto inherentemente implementa una variable de condicin y el " "clase proporciona
esttica"espera","pulso"y"PulseAll" mtodos para manipular la variable de condicin de un objeto.
pblico clase sellada Monitor { public static bool Wait(Object obj) {...} public static
voidPulse(Object obj) {...} public static voidPulseAll(Object obj) {...}...}

Un subproceso que llama " Espera"ya debe tener cerradura del objeto (de lo contrario, la
llamada"espera"producir una excepcin). El "espera" operacin atmico se desbloquea el objeto y
bloquea el subproceso*. Un subproceso que est bloqueado de esta manera se dice estar "esperando
en el objeto". El "pulso" mtodo no hace nada a menos que haya al menos un hilo esperando en el
objeto, en cuyo caso se despierta al menos una tal subproceso en espera (pero posiblemente ms de
uno). El "PulseAll"mtodo es como"pulso", salvo que se despierta todos los hilos actualmente
esperando en el objeto. Cuando un subproceso es despertado dentro "espera" despus de bloqueo,
relocks el objeto, entonces devuelve. Tenga en cuenta que la cerradura del objeto puede no estn
disponible inmediatamente, en cuyo caso el hilo recin despertado se bloquear hasta que el
bloqueo est disponible.
Si un subproceso llama a "Espera"cuando adquiri la cerradura del objeto varias veces,
el"Espera"mtodo comunicados (y ms adelante reacquires) el bloqueo que el nmero de veces.
Es importante ser consciente de que el hilo recin despertado podra no ser el siguiente
subproceso a adquirir el bloqueo: algn otro subproceso puede intervenir. Esto significa que podra
cambiar el estado de las variables protegido por el bloqueo entre la llamada de " "y el hilo de"esperar".

Esto tiene consecuencias que analizar en la seccin 4.6.


En sistemas predating Java, el "Espera"procedimiento o mtodo tom dos argumentos: una
cerradura y una variable de condicin; en Java y C#, stos se combinan en un nico argumento, que
es al mismo tiempo la cerradura y la cola de espera. En cuanto a los sistemas anteriores, esto
significa que el "Monitor" clase admite solamente una variable de condicin por cerradura .
Introduccin a la programacin con C# hilos . 7

Bloqueo del objeto protege los datos compartidos que se utilizan para la decisin de
programacin. Si un hilo A quiere el recurso, se bloquea el objeto apropiado y examina los datos
compartidos. Si el recurso est disponible, sigue el hilo. Si no, se desbloquea el objeto y bloques,
llamando "espera". Ms tarde, cuando algn otro hilo B pone a disposicin de los recursos que
despierta el hilo A llamando "pulso"o"PulseAll". Por ejemplo, podramos aadir lo siguiente
"GetFromList"mtodo de la clase"KV". Este mtodo espera hasta que la lista enlazada es nonempty y
luego elimina el elemento superior de la lista.
pblica esttica KV GetFromList() {KV res; cerradura(typeof(KV)) { mientras (cabeza == null)
Monitor.Wait (typeof(KV)); res = cabeza; cabeza = res.next; res.next = null;para la limpieza}
volver res; }

y el siguiente cdigo para el "AddToList"el mtodo podra ser utilizado por un hilo para agregar un
objeto a"cabeza"y despierta un hilo que estaba esperando lo
public void AddToList() { lock (typeof(KV)) {/ * estamos asumiendo esteprximo == null * /
estesiguiente = cabeza; cabeza = esta; Monitor.Pulse (typeof(KV)); } }

3.4. interrumpir un hilo


La parte final de la instalacin del hilo de rosca que voy a discutir es un mecanismo para
interrumpir un subproceso concreto, causando que se espera hacia atrs por un largo plazo. En el
sistema de ejecucin de C# esto es proporcionado por el hilo " interrumpir" mtodo:
pblico clase sellada Hilo { public void Interrupt() {...}...}

Si un subproceso "t"est bloqueado esperando a un objeto (es decir, est bloqueado en una llamada
de"Monitor.Wait") y otro hilo llamadas"t.Interrupt()", luego"t"se reanudar la ejecucin por relocking
del objeto (despus de esperar por la cerradura a ser desbloqueado, si es necesario) y luego
tirando"ThreadInterruptedException". (Lo mismo es cierto si el hilo se llama "Thread.Sleep"o"t.Join".)
Alternativamente, si "t" no espera un objeto (y no est durmiendo o esperando adentro " t.Join"),
entonces el hecho de 8 . Introduccin a la programacin con C# hilos

que "Interrumpir"ha sido llamado es registrado y a tirar del hilo"ThreadInterruptedException"la prxima


vez esperas o duerme.
Por ejemplo, considere un hilo "t"eso se llamaKVde "GetFromList"mtodo y se bloquea esperando
unKVobjeto a estar disponibles en la lista enlazada. Parece atractivo que si algn otro hilo del
cmputo decide el "GetFromList" llamada ya no es interesante (por ejemplo, el usuario hace clic en
Cancelar con su ratn), luego "t"debe devolver de"GetFromList". Si el manejo de hilos la Cancelar
solicitud pasa saber el objeto en el que "t"est esperando, y luego slo podra poner una bandera y
llamada"Monitor.Pulse" en ese objeto. Sin embargo, mucho ms a menudo la real llamada
"Monitor.Wait" se oculta bajo varias capas de abstraccin, completamente invisible para el hilo que se
encarga de la Cancelar solicitar. En esta situacin, el manejo de hilos la Cancelar peticin puede
alcanzar su objetivo llamando "t.Interrupt()". Por supuesto, en algn lugar de la pila de llamadas de
""debera haber un controlador para"ThreadInterruptedException". Exactamente lo que debes

hacer con la excepcin depende de su semntica deseada. Por ejemplo,


podramos arreglar eso una llamada interrumpida del "GetFromList"retorna"null":
pblica esttica KV GetFromList() {KV res = null; probar{ lock (typeof(KV)) { mientras
(cabeza == null) Monitor.Wait (typeof(KV)); res = cabeza; cabeza = head.next; res.next = null;
}} atrapar(ThreadInterruptedException) {} volver res; }

Las interrupciones son complicadas, y su uso produce programas complicados. Los analizaremos
ms detalladamente en la seccin 7.

4. USO DE CERRADURAS: ACCESO COMPARTIDO DATOS


La regla bsica para el uso de exclusin mutua es sencilla: en un programa de multithreaded
mutables compartidos todos los datos deben estar protegidos por asocindola con cerradura de
algn objeto, y usted deber acceder a los datos slo de un hilo que sostiene ese bloqueo (es decir,
desde un subproceso ejecutando dentro de un "cerradura" declaracin que bloquean el objeto).
4.1. proteccin de datos
El ms simple error relacionado con las cerraduras se produce cuando fallas proteger algunos datos
mutables y entonces puede acceder a l sin los beneficios de la sincronizacin. Por ejemplo,
considere el siguiente fragmento de cdigo. El campo " tabla"representa una tabla que puede ser
llenada con los valores del objeto llamando"Insertar". El "Insertar"mtodo funciona insertando un
objeto nonnull en el ndice de"yo"de"mesa", entonces incrementar"". La tabla est inicialmente vaca
(todos "null").Introduccin a la programacin con C# hilos . 9

clase de tabla {Tabla de objetos [] = nuevo objeto [1000]; int i = 0;


public void Insert (Object obj) { si (obj! = null) {(1) tabla [i] = obj; (2) i ++; } }
} clase de tabla

Ahora considerar qu pasara si llama hilo A "Insert(x)"simultneamente con hilo B


llamando"Insert(y)". Si el orden de ejecucin resulta ser que rosca A ejecuta (1), hilo B ejecuta (1) y
luego A hilo ejecuta (2), luego hilo B ejecuta (2), provocar la confusin. En lugar del efecto deseado
(que "x"y"y"se insertan en"mesa", en los ndices separados), el estado final que sera " y" est
correctamente en la tabla, pero "x" se ha perdido. Adems, desde el (2) ha sido ejecutado dos veces,
un vaco (nulo) ranura ha quedado hurfanos en la tabla. Estos errores podran haberlo evitados
adjuntando (1) y (2) en un "cerradura" declaracin, de la siguiente manera.
public void Insert (Object obj) { si (obj! = null) { cerradura(este) {(1) tabla [i] = obj; (2) i
++; } } }

El " cerradura "declaracin aplica la serializacin de las acciones de los hilos, para que un
subproceso ejecuta las sentencias dentro de la" cerradura "declaracin, entonces el otro subproceso
ejecuta.
Los efectos de sincronizacin acceso a datos mutables pueden ser extraos, ya que dependern
de la relacin de sincronizacin exacta entre tu ropa. En la mayora de los ambientes esta relacin
de sincronizacin es nondeterministic (debido a realtime efectos tales como errores de pgina o el
uso de instalaciones realtime temporizador) o asincrona real en un sistema de multiprocessor. En
un multiprocessor los efectos pueden ser especialmente difciles de predecir y comprender, porque
ellos dependen de los detalles de la consistencia de memoria de la computadora y algoritmos de
cach.
Sera posible disear un lenguaje que le permite asociar explcitamente variables con cerraduras
particulares y luego le impide acceder a las variables a menos que el hilo tiene la cerradura
adecuada. Pero C# (y la mayora de otros idiomas) no proporciona ninguna ayuda para esto: usted
puede elegir cualquier objeto alguno como el bloqueo de un determinado conjunto de variables.
Una manera alternativa para evitar el acceso no sincronizada es utilizar las herramientas de anlisis
esttico o dinmico. Por ejemplo, hay 10 . Introduccin a la programacin con C# hilos

Herramientas experimentales [19] ese cheque en tiempo de ejecucin que las cerraduras se
llevan a cabo durante el acceso a cada variable, y que advierten si se utiliza un conjunto
inconsistente de cerraduras (o sin cerradura en absoluto). Si usted tiene este tipo de herramientas
disponible, considerar seriamente usndolos. Si no, entonces usted necesita programador
considerable disciplina y uso cuidadoso de bsqueda y herramientas de navegacin. Acceso fuera
de sincronizacin, o mal sincronizado, se convierte cada vez ms probable como la granularidad
del bloqueo se convierte ms fino y sus reglas de bloqueo se correspondientemente ms complejos.
Tales problemas surgirn con menos frecuencia si utilizas muy simple, grueso grano, bloqueo. Por
ejemplo, utilizar la cerradura de la instancia del objeto a proteger todos los campos de instancia de
una clase y utilizar "typeof(clase)" para proteger los campos estticos. Por desgracia, bloqueo de
grano muy grueso puede causar otros problemas, que se describe a continuacin. As que el mejor
consejo es hacer el uso de bolsas de ser tan simple como sea posible, pero no ms simple. Si usted es
tentado a utilizar arreglos ms elaborados, estar completamente seguros que los beneficios valen los
riesgos, no slo que el programa luce mejor.
4.2. invariantes
Cuando los datos protegidos por una cerradura estn complicados en absoluto, muchos
programadores parece conveniente pensar en la cerradura como la proteccin de los invariantes de
los datos asociados. Una invariante es una funcin booleana de los datos que ocurre cuando la
cerradura asociada no se lleva a cabo. As que cualquier subproceso que adquiere la cerradura sabe
que empieza con la verdadera invariante. Cada hilo tiene la responsabilidad de restaurar la
invariante antes de soltar el bloqueo. Esto incluye la restauracin de la invariante antes de llamar
"espera", puesto que tambin libera el bloqueo.
Por ejemplo, en el fragmento de cdigo por encima (para insertar un elemento en una tabla), la
invariante es que "yo"es el ndice de la primera" null "elemento"mesa"y todos los elementos ms all
de ndice"yo"son" null ". Tenga en cuenta que las variables mencionadas en la invariante son
accesibles slo mientras "este" est cerrada. Tenga en cuenta tambin que no es cierto la invariante
despus de la primera instruccin de asignacin, pero antes que la segunda est garantizado slo
cuando el objeto est desbloqueado.
Con frecuencia los invariantes son bastante simples que apenas piensa en ellos, pero a menudo
su programa beneficiar de escribirlos explcitamente. Y si son demasiado complicados para anotar,
probablemente ests haciendo algo mal. Tal vez escribas abajo las invariantes de manera informal,
al igual que en el prrafo anterior, o puede usar un lenguaje de especificacin formal. A menudo es
sensato tener tu programa comprobar explcitamente sus invariantes. Tambin es generalmente una
buena idea indicar explcitamente, en el programa, que la cerradura protege los campos.
Independientemente de cmo formalmente te gusta pensar de invariantes, tienes que ser
consciente del concepto. Liberando la cerradura mientras que las variables estn en un estado
incoherente transitorio conducir inevitablemente a la confusin si es posible que otro subproceso
adquirir la cerradura mientras ests en este estado.
4.3. bloquea que involucra slo las cerraduras
En algunos sistemas de hilo [4] su programa ser deadlock si un subproceso intenta bloquear un
objeto que ya est cerrada. C# (y Java) explcitamente permiten que un subproceso bloquear un
objeto varias veces de manera anidada: el sistema runtime realiza un seguimiento de qu

subproceso ha bloqueado el objeto y con qu frecuencia. El objeto permanece bloqueado


Introduccin a la programacin con C# hilos . 11

(y por lo tanto est bloqueado el acceso simultneo por otros subprocesos) hasta que el hilo haya
destrabado el objeto el mismo nmero de veces.
Esta funcin 'bloqueo de reentrant' es una conveniencia para el programador: desde dentro un
" cerradura "declaracin puede llamar a otro de sus mtodos que tambin bloquea el mismo objeto,
sin riesgo de bloqueo. Sin embargo, la caracterstica es doubleedged: si se llama al mtodo otro en
un momento cuando las invariantes monitor no son verdaderas, entonces el otro mtodo ser
probable que se portan mal. En sistemas que prohben reentrant bloqueo tal conducta es
prevenida, siendo reemplazado por un callejn sin salida. Como he dicho antes, estancamiento
suele ser un bicho ms agradable que devolver la respuesta equivocada.
Existen numerosos casos ms elaborados de bloqueo que involucra slo las cerraduras, por
ejemplo:
cerraduras de hilo A objecin M1; cerraduras de hilo B objecin M2; bloques de hilo A intentar
bloquear M2; hilo bloques B tratando de cerradura M1.

La regla ms eficaz para evitar tales bloqueos es tener un orden parcial para la adquisicin de
bloqueos en su programa. En otras palabras, hacer que para cualquier par de objetos {M1, M2}, cada
hilo que necesite tener M1 y M2 cerrada al mismo tiempo lo hace mediante el bloqueo de los objetos
en el mismo orden (por ejemplo, M1 siempre est cerrada antes de M2). Esta regla evita totalmente
los interbloqueos que involucra slo las cerraduras (aunque como veremos ms adelante, hay otros
posibles bloqueos cuando su programa usa el "Monitor.Wait" mtodo).
Hay una tcnica que a veces resulta ms fcil lograr este orden parcial. En el ejemplo anterior,
hilo A probablemente no estaba tratando de modificar exactamente el mismo conjunto de datos
como hilo B. con frecuencia, si examina cuidadosamente el algoritmo puede particionar los datos en
trozos ms pequeos protegidos por separado cerraduras. Por ejemplo, cuando hilo B intent
cerradura M1, podra en realidad quiero acceso datos separados de los datos que estaba accediendo
A hilo bajo M1. En tal caso podra proteger estos datos inconexos bloqueando un objeto separado,
M3 y evitar el estancamiento. Tenga en cuenta que esto es slo una tcnica para permitirle tener un
orden parcial en las cerraduras (M1 antes M2 antes de M3, en este ejemplo). Pero recuerde que
cuanto ms te dedicas a esta pista, la ms complicada que su fijacin se convierte, y ms probable es
que se confunda acerca de que la cerradura est protegiendo los datos y terminan con algn
sincronizado acceso a datos compartidos. (mencion que tener su estancamiento programa casi
siempre es un riesgo preferible a tener su programa dar la respuesta equivocada?)
4.4. bajo rendimiento a travs de los conflictos de la cerradura
Suponiendo que haya dispuesto su programa para tener suficientes candados que todos los datos
estn protegidos y una lo suficientemente fina granularidad que no deadlock, los restantes
problemas bloqueo preocuparse son todos los problemas de rendimiento.
Cuando un subproceso tiene una cerradura, potencialmente se detiene otro subproceso de
progreso si los otros bloques de hilo tratando de adquirir el bloqueo. Si el primer subproceso
puede utilizar los recursos de la mquina, est bien. Pero si 12 . Introduccin a la programacin con C#
hilos

el primer subproceso, manteniendo el bloqueo, deja de avanzar (por ejemplo mediante el


bloqueo en la otra cerradura, o tomando un fallo de pgina o esperando a un dispositivo de
entrada-salida), luego se degrada el rendimiento total de su programa. El problema es peor en un
multiprocessor, donde no hay un nico hilo puede utilizar toda la mquina; aqu si usted causa
otro subproceso se bloquee, podra significar que un procesador pasa inactivo. En general, para
obtener buen rendimiento se deben organizar que Trabe los conflictos son eventos raros. Es la mejor
manera de reducir los conflictos de bloqueo bloquear en una granularidad ms fina; Pero esto
presenta complejidad y aumenta el riesgo de no sincronizado acceso a datos. No hay forma de este
dilema es un tradeoff inherentes en computacin concurrente.
El ejemplo ms tpico donde el bloqueo de granularidad es importante en una clase que
gestiona un conjunto de objetos, por ejemplo un conjunto de abrir archivos tamponados. La
estrategia ms simple es usar una sola cerradura global para todas las operaciones: abrir, cerrar,
leer, escribir y as sucesivamente. Pero esto impedira varias escrituras en archivos separados
proceder en paralelo, por ninguna razn. Una estrategia mejor es utilizar una cerradura para
operaciones en la lista mundial de archivos abiertos, y un candado por abrir el archivo para las
operaciones que afectan slo a ese archivo. Afortunadamente, sta tambin es la manera ms obvia
de utilizar las cerraduras en una lengua objectoriented: el bloqueo global protege las estructuras de
datos globales de la clase, y bloqueo de cada objeto se utiliza para proteger los datos especficos de
esa instancia. El cdigo puede parecer algo como lo siguiente.
clase F {cabeza esttica F = null; / / protegido por typeof(F) string minombre; / / inmutable F
siguiente = null; / / protegido por datos D typeof(F); / / protegido por "esto"
pblica esttica F Abierto (string nombre) { lock (typeof(F)) { para (F f = cabeza; f! = null; f =
f.next) {si (nombre.Equals(f.myname)) volver f; } / / Si obtiene un nuevo F, enqueue en "cabeza"
y devolverlo. volver ...; } }
public void Escritura (F f, string msg) { cerradura (este) {/ / acceso "f.data"}}
}

All es una importante sutileza en el ejemplo anterior. La forma que eleg implementar la lista
global de archivos era pasar una lista vinculada a travs de la " siguiente" campo de instancia. Esto
dio como resultado un ejemplo donde parte de los datos de instancia debe Una introduccin a la
programacin con C# hilos . 13
* Recordar que la prioridad del hilo de rosca es no un mecanismo de sincronizacin: un hilo de alta prioridad
puede conseguir fcilmente superado por un hilo de prioridad ms bajo, por ejemplo si los hilos de alta
prioridad choca con un fallo de pgina.

estar protegido por el bloqueo global y la parte por el percerradura de instancia object. Este es slo
uno de una amplia variedad de situaciones donde usted puede optar por proteger a diferentes
campos de un objeto con diversas cerraduras, para conseguir mayor eficiencia accediendo
simultneamente desde diferentes subprocesos.
Por desgracia, este uso tiene algunas de las mismas caractersticas de sincronizacin acceso a
datos. La correccin del programa se basa en la capacidad de acceder a diferentes partes de la
memoria del ordenador simultneamente desde diferentes subprocesos, sin interferir mutuamente
los accesos. El modelo de memoria Java especifica que esto funcionar correctamente mientras las
diversas cerraduras protegen diferentes variables (por ejemplo, campos de instancia diferente). Sin
embargo, la especificacin de lenguaje C#, es actualmente silenciosa sobre este tema, as que usted
debe programar conservador. Recomiendo que usted asume accesos para referencias a objetos y

valores escalares de 32 bits o ms (por ejemplo, "int"o"flotador") puede continuar


independientemente bajo diversas cerraduras, pero que tiene acceso a valores ms pequeos (como
"bool") tal vez no. Y no sera ms prudente acceder a distintos elementos de una matriz de valores
pequeos tales como "bool" bajo diversas cerraduras.
All es una interaccin entre bloqueos y el planificador de hilo que puede producir problemas de
rendimiento particularmente insidiosa. El programador es la parte de la implementacin del hilo de
rosca (a menudo parte del sistema operativo) que decide cul de los nonblocked hilos en realidad
deberan darse un procesador para correr en. Generalmente el programador hace su decisin
basndose en una prioridad asociada a cada subproceso. (C# le permite ajustar la prioridad de un
hilo mediante la asignacin de la rosca "Prioridad"propiedad*.) Cerradura conflictos pueden llevar a
una situacin donde algunos hilos de alta prioridad nunca avanza, a pesar de que su prioridad alta
indica que es ms urgente que los threads ejecutndose en realidad.
Esto puede suceder, por ejemplo, en el siguiente escenario en un uniprocessor. Hilo A es de
alta prioridad, hilo B es prioridad media y rosca C es una prioridad baja. La secuencia de eventos
es:
C se est ejecutando (por ejemplo, porque estn bloqueados en alguna parte A y B); C las
cerraduras objeto M; B despierta y descarta C (es decir, B funciona en vez de C puesto que B
tiene mayor prioridad); B se embarca en un clculo muy larga; A se despierta y descarta B (ya
que tiene mayor prioridad); Un intenta bloquear M, pero no puede porque an est bloqueado
por C; Por bloques, as que el procesador es devuelto a B; B contina su cmputo muy largo.

Efecto la red es que un hilo de alta prioridad (A) es incapaz de progresar a pesar de que el
procesador est siendo utilizado por un hilo de prioridad media (B). Este estado es 14 . Introduccin a
la programacin con C# hilos

estable hasta que no haya tiempo de procesador disponible para el hilo de baja prioridad C
completar su trabajo y desbloquear M. Este problema se conoce como "inversin prioritaria".
El programador puede evitar este problema arreglando para que C elevar su prioridad antes de
bloquear M. Pero esto puede ser bastante inconveniente, ya que se trata de considerar para cada
cerradura que otras prioridades del hilo pueden estar involucrados. La mejor solucin a este
problema se encuentra en programador de subproceso del sistema operativo. Idealmente, debera
plantear artificialmente prioridad C mientras que eso es necesario para permitir A progresar con el
tiempo. El programador de Windows NT no hace esto, pero arregla que hilos incluso baja prioridad
avanzar, slo a un ritmo ms lento. C eventualmente completar su trabajo y A hacer progresos.
4.5. liberando el bloqueo dentro de un "cerradura" declaracin
Hay veces cuando usted quiere desbloquear el objeto en algunas regiones del programa anidado
dentro de un " cerradura "declaracin. Por ejemplo, deberas desbloquear el objeto antes de llamar
una abstraccin de nivel inferior que se bloquean o ejecutar durante mucho tiempo (para evitar
provocar retrasos en otros subprocesos que desea bloquear el objeto). C# (pero no Java) ofrece para
este uso ofreciendo las operaciones de crudo "Enter(m)"y"Exit(m)" como mtodos estticos de la
"Monitor" clase. Usted debe ejercer cuidado extra si te aprovechas de esto. En primer lugar, usted
debe estar seguro de que las operaciones estn correctamente entre corchetes, incluso en presencia
de excepciones. En segundo lugar, debe estar preparado para el hecho de que podra haber
cambiado el estado de los datos del monitor cuando tuvieron el objeto desbloqueado. Esto puede
ser difcil si usted llama "salida" explcitamente (en lugar de solo poner fin a la "cerradura"
declaracin) en un lugar donde fueron encajadas en un control de flujo construir como una clusula
condicional. El contador de programa ahora puede depender el estado previo de los datos del
monitor, implcitamente tomando una decisin que ya no puede ser vlida. As desanimo a este
paradigma, para reducir la tendencia a presentar errores muy sutiles.
Algunos hilos sistemas, aunque no C#, permiten un otro uso de llamadas separadas del
"Enter(m)"y"Exit(m)", en las cercanas de bifurcar. Podras estar ejecutando con un objeto bloqueado y
quiero un nuevo hilo para seguir trabajando en los datos protegidos, mientras que el hilo original
contina sin mayor acceso a los datos de la bifurcacin. En otras palabras, desea transferir la
celebracin de la cerradura a la rosca recin bifurcada, atmicamente. Esto puede lograr mediante
el bloqueo del objeto con "Enter(m)" en vez de un ""Declaracin y ms tarde llamada"Exit(m)" en el

hilo de traccin. Esta tctica es muy peligrosa, es difcil de verificar el correcto


funcionamiento del monitor. Te recomiendo que no lo haces incluso en sistemas
que le permiten (a diferencia de C#).
4.6. sin bloqueo de programacin
Como hemos visto, utilizando las cerraduras es un arte delicado. Adquirir y liberar bloqueos
ralentiza su programa, y algunos usos de cerraduras inadecuados pueden producir rendimiento
espectacularmente grandes sanciones, o incluso muerto. A veces los programadores responden a
estos problemas tratando de escribir programas concurrentes que son correctos sin necesidad de
utilizar las cerraduras. Esta prctica es general llamado "programacin de lockfree". Requiere
aprovechando la atomicidad de ciertas operaciones primitivas, o usando a lowerlevel primitivas
tales como instrucciones de barrera de memoria. Introduccin a la programacin con C# hilos . 15

* La especificacin del lenguaje Java tiene un modelo de memoria razonablemente precisa [7, captulo 17], que
dice, aproximadamente, que se acceden atmicamente referencias a objetos y cantidades escalares no mayores
a 32 bits. C# todava no tiene un modelo de memoria definida con precisin, que hace lockfree programacin
an ms arriesgado.

Con mquina moderna arquitecturas y compiladores modernos, esto es algo sumamente


peligroso. Los compiladores son libres de reorder acciones dentro de la semntica formal
especificada del lenguaje de programacin y la voluntad a menudo hacen. Esto lo hacen por
razones sencillas, como mover Cdigo fuera de un bucle, o para los ms sutiles, como optimizar el
uso de la memoria cach de un procesador onchip o tomando ventaja de los ciclos de la mquina lo
contrario ociosa. Adems, las arquitecturas de mquina multiprocessor tienen increblemente
complejas reglas para cuando los datos se mueven entre cachs de procesador y la memoria
principal, y cmo esto se sincroniza entre los procesadores. (Incluso si alguna vez un procesador no
hace referencia a una variable, la variable podra ser en la memoria cach del procesador, si la
variable es en la misma lnea de cach que alguna otra variable que el procesador hizo referencia.)
Es, sin duda, posible escribir programas lockfree correcto, y hay muchas investigaciones
actuales sobre cmo configurar este [10]. Pero es muy difcil y es muy poco probable que usted
conseguir lo correcto si no utilizas tcnicas formales para verificar esa parte de su programa.
Tambin tienes que estar muy familiarizado con el modelo de memoria de su lenguaje de
programacin.[15]* Mirar en tu programa y conjeturar (o discutiendo informalmente) que es
correcto es probable que resulte en un programa que funciona correctamente casi todo el tiempo,
pero muy ocasionalmente misteriosamente obtiene la respuesta equivocada.
Si ests siendo tentado a escribir cdigo lockfree a pesar de estas advertencias, por favor
primero considere cuidadosamente si es preciso su creencia de que las cerraduras son demasiado
caras. En la prctica, muy pocas piezas de un programa o sistema son realmente crticas para su
desempeo. Sera triste reemplazar un programa correctamente sincronizado con un lockfree de
almostcorrect uno y luego descubrir que aun cuando su programa no se rompe, su rendimiento no
es significativamente mejor.
Si usted busca la web para algoritmos de sincronizacin lockfree, la que ms frecuentemente se
hace referencia se llama "Algoritmo de Peterson" [16]. Hay varias descripciones de l en los sitios
web de la Universidad, acompaados a menudo informales "pruebas" de su correccin. En
dependencia de stos, los programadores han intentado mediante este algoritmo en el cdigo
prctica, slo para descubrir que su programa ha desarrollado las condiciones de carrera sutil. La
resolucin de esta paradoja se encuentra en la observacin de que las "pruebas" dependen,
generalmente implcitamente, presunciones sobre la atomicidad de las operaciones de memoria.
Algoritmo de Peterson publicado se basa en un modelo de memoria conocido como "consistencia
secuencial" [12]. Por desgracia, no multiprocessor moderna proporciona consistencia secuencial,
porque la pena de rendimiento en su memoria subsystem sera demasiado grande. No lo hagas.
Otra tcnica de lockfree que la gente trata a menudo se llama "bloqueo doublecheck". La intencin
es inicializar una variable compartida poco antes de su primer uso, de tal manera que la variable
puede accederse posteriormente sin necesidad de utilizar un "cerradura" declaracin. Por ejemplo,
considere el siguiente cdigo. 16 . Introduccin a la programacin con C# hilos
* Esta discusin es bastante diferente de la correspondiente en la versin de 1989 de este documento [2]. La
diferencia de velocidad entre los procesadores y su memoria principal ha aumentado tanto que ha impactado
los problemas de diseo highlevel a escribir programas concurrentes. Tcnicas de programacin que
anteriormente eran correctas se han convertido en incorrectas.
Foo theFoo = null;
pblico Foo GetTheFoo() { si (theFoo == null) { cerradura (este) { si (theFoo == null) theFoo =
new Foo();}} volver theFoo; }

Intencin del programador aqu es que la primera rosca para llamar " GetTheFoo"causar el objeto
requerido ser creado y inicializado y todas las llamadas subsiguientes de" GetTheFoo"regresar este
mismo objeto. El cdigo es tentador, y sera correcto con los compiladores y multiprocessors de los
aos 80.* Hoy, en la mayora de los idiomas y en casi todas las mquinas, es incorrecto. Las
deficiencias de este paradigma se han discutido ampliamente en la comunidad de Java [1]. Hay dos
problemas.
Primera, si "GetTheFoo"se llama el procesador A y entonces ms adelante procesador B, es
posible que procesador B ver el objeto correcto nonnull de referencia para" theFoo", pero va a leer
correctamente almacenados en memoria cach los valores de las variables de instancia dentro
de"theFoo", porque llegaron en cach de B en algn momento anterior, estando en la misma lnea de
cach que alguna otra variable que se almacena en cach en B.
En segundo lugar, es legtimo que el compilador reorder declaraciones dentro de un "
cerradura "declaracin, si el compilador puede demostrar que no interfieren. Considere lo que
podra ocurrir si el compilador convierte el cdigo de inicializacin para " nuevo Foo()" en lnea, y
luego reorders las cosas para que la asignacin a "theFoo"ocurre antes de la inicializacin de la
variable instancia de"theFoo". Un hilo cronolgico en otro procesador entonces podra ver un non
null "theFoo" antes el objeto instancia est correctamente inicializado.
Hay muchas maneras que la gente ha intentado arreglar este [1]. Se equivocan. La nica manera
que usted puede estar seguro de que este cdigo funcione es obvia, dnde te envuelves todo en un
"cerradura" declaracin:
Foo theFoo = null;
pblico Foo GetTheFoo() { cerradura (este) { si (theFoo == null) theFoo = new Foo(); volver
theFoo; }} Introduccin a la programacin con C# hilos . 17
*Las variables condiciones descritas aqu no son las mismas que las descritas originalmente por
Hoare [9].Diseo de Hoare hecho proporcionara una garanta suficiente para hacer este re
testing redundante. Pero el diseo dado aqu parece ser preferible, ya que permite una
implementacin mucho ms simple, y el cheque adicional no es generalmente muy caro.

De hecho, C# hoy es implementado de una manera que hace doblecheck de bloqueo funcione
correctamente. Pero confiando en que esto parece una manera muy peligrosa al programa.

5. UTILIZACIN DE ESPERA Y PULSO: PROGRAMACIN DE RECURSOS COMPARTIDOS


Cuando desee programar la forma en la que varios subprocesos acceder a un recurso compartido, y
la exclusin mutua oneatatime simple de cerraduras no es suficiente, usted querr hacer su
bloque de hilos en espera de un objeto (el mecanismo llamado "variables de estado" en otros
sistemas del hilo de rosca).
Recordar el "GetFromListmtodo de mi anteriorKV"ejemplo. Si la lista enlazada est vaca,
"GetFromList"bloques hasta"AddToList" genera algunos datos ms:
cerradura (typeof(KV)) { mientras (cabeza == null) Monitor.Wait (typeof(KV)); res = cabeza;
cabeza = res.next; res.next = null; }

Esto es bastante sencillo, pero todava hay algunas sutilezas. Observe que cuando un subproceso
regresa de la llamada "espera" su primera accin despus de relocking el objeto es verificar una vez

ms si est vaca la lista enlazada. Este es un ejemplo de la siguiente pauta general, que recomiendo
para todo su uso de variables de condicin:
mientras (! expresin) Monitor.Wait(obj);

Usted podra creer que retesting la expresin es redundante: en el ejemplo anterior,


"AddToList"hecha la lista nonempty antes de llamar"Pulso". Pero la semntica de "pulso" no
garantizan que el hilo despertado ser la prxima para bloquear el objeto. Es posible que algn otro
hilo consumidor intervendr, bloquear el objeto, quitar el elemento de lista y desbloquear el objeto,
antes de que el hilo recin despertado puede bloquear el objeto.* Un beneficio secundario de esta
regla de programacin es que permitira la aplicacin de " Pulso"para despertar (raramente) ms de
un hilo; Esto puede simplificar la implementacin de " espera", aunque ni Java ni C# en realidad dar
tanta libertad el implementador hilos.
Pero la razn principal para defender la utilizacin de este patrn es hacer su programa ms
evidente y ms slidamente, corregir. Con este estilo es inmediatamente evidente que la expresin
es verdadera antes de ejecucin los siguientes comandos. Sin l, este hecho podra verificarse
solamente por mirar a todos los lugares que podran pulso el objeto. En otras palabras, este
Convenio de programacin le permite verificar la correccin por la inspeccin de local, que siempre
es preferible a la inspeccin global. 18 . Introduccin a la programacin con C# hilos
* El C# runtime incluye una clase para hacer esto, "ReaderWriterLock". Persigo este ejemplo aqu en parte
porque los mismos problemas se presentan en un montn de problemas ms complejos y en parte porque la
especificacin de "ReaderWriterLock" guarda silencio sobre cmo o si su aplicacin aborda los temas que vamos
a discutir. Si te preocupas por estas cuestiones, tal vez encuentre que su propio cdigo funcionar mejor que
"ReaderWriterLock".

Una ventaja final de esta Convencin es que permite una sencilla programacin de llamadas a
"Pulso"o"PulseAll" wakeups extra son benignos. Codificacin cuidadosamente para asegurar que
slo los hilos correctos estn despertados ahora es slo una cuestin de rendimiento, no una
correccin uno (pero por supuesto que debe asegurarse de que al menos se despert los hilos
correctos).
5.1. usando "PulseAll"
El "Pulso"primitivo es til si usted sabe que a lo sumo un subproceso puede ser despertado
provechosamente."PulseAll"despierta todas las roscas que han llamado"Espera". Si usted siempre
programar en el estilo recomendado de rechecking una expresin despus de regresar de "espera",
entonces la correccin de su programa ser afectada si reemplazas las llamadas de" pulso"con
llamadas de"PulseAll".
Un uso de "PulseAll"es cuando quieres simplificar su programa al despertar varios subprocesos,
aunque sabe que no todos pueden progresar. Esto le permite ser menos cuidadosos acerca de
separar espera diferentes razones en diferentes colas de espera hilos. Este uso cotiza un rendimiento
ligeramente ms pobre para mayor simplicidad. Otro uso del " PulseAll" es cuando realmente
necesitas despertar varios subprocesos, porque el recurso que acaba de hacer disponible puede ser
utilizado por varios otros subprocesos.
Un ejemplo sencillo donde "PulseAll"es til en la planificacin poltica conocida como exclusiva
compartida de bloqueo (o lectores/escritores de bloqueo). Comnmente se utiliza cuando tienes
algunos datos compartidos ser ledo y escrito por varios subprocesos: su algoritmo ser correcto (y
tener un mejor desempeo) si permites que varios subprocesos leer los datos simultneamente,

pero un hilo de modificacin de los datos debe hacerlo cuando ningn otro subproceso es acceder a
los datos.
Los siguientes mtodos de implementacin de esta poltica de programacin*. Cualquier hilo de
querer leer sus llamadas de datos "AcquireShared", y luego Lee los datos, entonces se llama
"ReleaseShared". Asimismo cualquier hilo de querer modificar las llamadas de datos
"AcquireExclusive", entonces modifica los datos, entonces se llama "ReleaseExclusive". Cuando la
variable "" es mayor que cero, cuenta el nmero de lectores activos. Cuando es negativo hay un
escritor activo. Cuando es cero, no hay hilo est utilizando los datos. Si un lector potencial dentro
de "AcquireShared"que se encuentra"" es menor que cero, debe esperar hasta que el escritor llama
"ReleaseExclusive".
clase RW { int i = 0; / / protegido por "esto"
public void AcquireExclusive() { cerradura (este) { mientras (yo! = 0) Monitor.Wait (este);
Introduccin a la programacin con C# hilos . 19

Te = -1; } }
public void AcquireShared() { cerradura (este) { mientras (yo < 0) Monitor.Wait (este); i ++;}}
public void ReleaseExclusive() { cerradura (este) {i = 0; Monitor.PulseAll (este); } }
public void ReleaseShared() { cerradura (este) {i--; si(i == 0) Monitor.Pulse (este); } }
} / / clase RW

Usando "PulseAll"es conveniente en"ReleaseExclusive", porque un escritor terminacin no necesita


saber cuntos lectores ahora son capaces de proceder. Pero aviso que usted podra recode este
ejemplo utilizando slo "pulso", mediante la adicin de un contador de cmo muchos lectores estn
esperando y llamando "Pulso" muchas veces "ReleaseExclusive". El "PulseAll" es slo una
conveniencia, aprovechndose de la informacin ya disponible para la ejecucin de subprocesos.
Observe que no hay ninguna razn para usar "PulseAll"en"ReleaseShared", porque sabemos que a lo
sumo un escritor bloqueado puede avanzar provechosamente.
Esta codificacin particular de la fijacin exclusiva compartida ejemplifica muchos de los
problemas que pueden ocurrir cuando espera en objetos, como veremos en las siguientes secciones.
Como discutimos estos problemas, yo presentar codificaciones revisadas de este paradigma
bloqueo.
5.2. espurios Estela-ups
Si mantienes el uso del "Espera"muy simple, usted puede introducir la posibilidad de despertar los
subprocesos que no se pueden progresar til. Esto puede suceder si utilizas " PulseAll"cuando"pulso"
sera suficiente, o si tiene hilos esperando en un solo objeto por mltiples razones diferentes. Por
ejemplo, los mtodos de fijacin exclusiva compartida arriba tienen lectores y escritores tanto
espera "este". Esto significa que cuando llamamos "PulseAll"en"ReleaseExclusive", el efecto ser 20 .
Introduccin a la programacin con C# hilos
* Es ms difcil en Java, que no proporciona "Monitor.Enter"y"Monitor.Exit".

despertar ambas clases de subprocesos bloqueados. Pero si un lector es el primero en cerrar el


objeto, se incrementar "i"y evitar que un escritor potencial despertado de avanzar hasta el lector
ms tarde llamadas"ReleaseShared". El costo de esto es tiempo extra que pas en el programador
del hilo de rosca, que normalmente es un lugar caro para ser. Si tu problema es tal que estos falsos
wakeups ser comn, entonces realmente quieres dos lugares para esperar uno para los lectores
y otro para los escritores. Slo necesita llamar a un lector de terminacin " pulso" en el objeto donde
esperan escritores; un escritor terminacin llamara "PulseAll" en uno de los objetos, dependiendo de
la era nonempty.
Por desgracia, en C# (y en Java) para cada cerradura slo podemos esperar en un objeto, la
misma que estamos utilizando como la cerradura. Para programar alrededor de esto tenemos que
usar un segundo objeto y su cerradura. Es sorprendentemente fcil de hacerlo mal, generalmente
mediante la introduccin de una carrera donde cierto nmero de hilos se ha comprometido a
esperar en un objeto, pero no tienen suficiente de una cerradura llev a cabo para evitar que algn
otro llamado hilo "PulseAll" sobre ese objeto, y as el wakeup se pierde y los impasses del programa.
Creo que los siguientes "CV"clase, como se utiliza en la siguiente revisin"RW " ejemplo, obtiene esta
bien (y usted debe ser capaz de reuse este exacta "CV" clase en otras situaciones).*

clase CV {objeto m; / / la cerradura asociados con este CV pblico CV (objeto m) {/ /


Constructor cerradura(este) esta.m = m;}
public void Wait() {/ / Pre: este hilo tiene "m" exactamente una vez bool entrar = false; / /
usando la bandera de "entrar" da error de limpieza manejo si m no es bloqueado intenta {
cerradura (este) {Monitor.Exit(m); entrar = true; Monitor.Wait (este); finalmente {}} { si (entrar)
Monitor.Enter(m);}}
public void Pulse() { bloqueo (esta) Monitor.Pulse (este);}
public void PulseAll() { bloqueo (esta) Monitor.PulseAll (este);}
} / / clase CV una introduccin a la programacin con C# hilos . 21

Ahora podemos revisar "RW"para arreglar que slo espera los lectores esperen en la
principal"RW "objeto, y que espera escritores esperen el auxiliar"wQueue"objeto. (Inicializacin
"wQueue"es un poco complicado, ya que nosotros no podemos hacer referencia" esto "al inicializar
una variable de instancia.)
clase RW { int i = 0; / / protegido por "esta" int readWaiters = 0; / / protegido por "esto" wQueue
CV = null;
public void AcquireExclusive() { cerradura (este) { si (wQueue == null) wQueue = nuevo CV
(esto) mientras que (yo! = 0) wQueue.Wait(); i = -1;}}
public void AcquireShared() { cerradura (este) {readWaiters ++; mientras(yo < 0) Monitor.Wait
(este); readWaiters--; i ++; } }
public void ReleaseExclusive() { cerradura (este) {i = 0; si(readWaiters > 0) {Monitor.PulseAll
(este);} ms{ si (wQueue! = null) wQueue.Pulse();} } }
public void ReleaseShared() { cerradura (este) {i--; si(i == 0 & & wQueue! = null)
wQueue.Pulse(); } }
} / / clase RW 22 . Introduccin a la programacin con C# hilos

5.3. los conflictos bloqueo espurias


Otra fuente potencial de programacin excesiva sobrecarga proviene de situaciones donde se
despierta un hilo de esperar en un objeto y antes de hacer trabajo til el subproceso se bloquee
tratando de bloquear un objeto. En algunos diseos de hilo, esto es un problema en la mayora
wakeups, porque el hilo despert inmediatamente tratar de adquirir el bloqueo asociado a la
variable de condicin, que se lleva a cabo actualmente por el subproceso haciendo el wakeup. C#
evita este problema en casos simples: llamando " Monitor.Pulse" en realidad no deja el hilo despierto
empezar a ejecutar. En cambio, se transfiere a una cola de"lista" en el objeto. La cola de lista consta
de hilos que estn dispuestos a cerrar el objeto. Cuando un subproceso abre el objeto, como parte de
esa operacin tomar un hilo en la cola de listo e iniciarlo ejecutando.
Sin embargo todava hay un conflicto de bloqueo espurias en el " RW "clase. Cuando un escritor
terminacin interior "ReleaseExclusive"llamadas"wQueue.Pulse(this)", todava tiene"este" bloqueado.
En un uniprocessor esto a menudo no sera un problema, pero en un multiprocessor el efecto es
probable que haya que un escritor potencial es despertado dentro " CV.Espera", se ejecuta tanto
como la"finalmente"cuadra y luego intentando bloquear"m" porque ese bloqueo se mantiene
todava por el escritor terminacin, ejecutando simultneamente. Unos microsegundos despus el
escritor terminacin abre el "RW " objeto, permitiendo que el escritor continuar. Esto nos ha costado
dos operaciones reschedule adicional, que es un gasto significativo.
Afortunadamente hay es una solucin simple. Puesto que el escritor terminacin no acceder
a los datos protegidos por el bloqueo despus de la llamada " wQueue.Pulse", podemos pasar la
llamada a despus del final de la "cerradura" declaracin, como sigue. Observe que acceder a ""
todava est protegido por la cerradura. Una situacin similar ocurre en " ReleaseShared".
public void ReleaseExclusive() { bool doPulse = false; cerradura(este) {i = 0; si(readWaiters >
0) {Monitor.PulseAll (este);} ms{doPulse = (wQueue! = null);} } wQueue.Pulse() si (doPulse); }
public void ReleaseShared() { bool doPulse = false; cerradura(este) { i--; si(i == 0) doPulse =
(wQueue! = null); } Introduccin a la programacin con C# hilos . 23

si

wQueue.Pulse() (doPulse);}

Hay situaciones potencialmente an ms complicadas. Cmo obtener el mejor rendimiento es


importante para su programa, debes considerar cuidadosamente si un subproceso recin despierto
necesariamente se bloquear en algn otro objeto poco despus de que empiece a correr. Si es as,
tienes que organizar para aplazar la wakeup para un momento ms adecuado. Afortunadamente,
la mayora del tiempo en C# la lista cola utilizada por " Monitor.Pulse" har lo correcto para usted
automticamente.
5.4. hambre

Cada vez que tiene un programa que toma decisiones de planificacin, debes preocuparte sobre
cmo justo estas decisiones son; en otras palabras, son iguales todos los subprocesos o son un poco
ms favorecidas? Cuando usted bloquee un objeto, esta consideracin se aborda por ti mediante la
implementacin de hilos tpicamente por una regla de firstinfirstout para cada nivel de
prioridad. Sobre todo, esto tambin es vlido cuando se utiliza " Monitor.Wait" en un objeto. Pero a
veces el programador debe involucrarse. La forma ms extrema de la injusticia es "inanicin",
donde algunos hilos voluntad nunca avanzar. Esto puede surgir en nuestro ejemplo bloqueo reader
writer (por supuesto). Si el sistema est cargado, as que siempre hay al menos un hilo de querer ser
un lector, el cdigo vigente se morirn de hambre a escritores. Esto podra ocurrir con el siguiente
patrn.
Rosca A llamadas "AcquireShared"; Yo: = 1; Hilo B llamadas "AcquireShared"; Yo: = 2; Rosca A
llamadas "ReleaseShared"; Yo: = 1; Hilo C llamadas "AcquireShared"; Yo: = 2; Hilo B llamadas
"ReleaseShared"; Yo: = 1; ... etc.

Ya que siempre hay un lector activo, nunca hay un momento cuando un escritor puede proceder;
potenciales escritores siempre permanecer cerrados, esperando " yo"para reducir a 0. Si la carga es
tal que esto es realmente un problema, tenemos que hacer el cdigo an ms complicado. Por
ejemplo, podemos organizar que un nuevo lector podra aplazar dentro " AcquireShared" si hubo un
escritor potencial bloqueado. Podramos hacer esto mediante la adicin de un contador para los
escritores bloqueados, como sigue.
int writeWaiters = 0; public voidAcquireExclusive() { cerradura (este) { si (wQueue == null)
wQueue = nuevo CV (esto);writeWaiters ++; mientras(yo! = 0) wQueue.Wait(); writeWaiters--;
Te = -1; }} 24 . Introduccin a la programacin con C# hilos

public void AcquireShared() { cerradura (este) {readWaiters ++; si(writeWaiters > 0)


{wQueue.Pulse(); Monitor.Wait(this); } mientras (yo < 0) Monitor.Wait (este); readWaiters--; i ++;
}}

No hay lmite a lo complicado esto puede llegar a ser, implementando cada vez ms elaborado
polticas de programacin. El programador debe actuar con moderacin y slo agregar
funcionalidades si realmente estn obligados por la carga real en el recurso.
5.5. complejidad
Como puedes ver, preocuparse por estos falsos wakeups, cerradura de conflictos y el hambre hace
que el programa ms complicado. La primera solucin del problema lector/grabador que le mostr
tena 16 lneas dentro de los cuerpos de mtodo; la versin final tena 39 lneas (incluyendo el " CV"
clase) y algunos razonamientos muy sutiles sobre su correccin. Tienes que tener en cuenta, para
cada caso, si el costo potencial de ignorar el problema es suficiente para merecer escribiendo un
programa ms complejo. Esta decisin depender de las caractersticas de rendimiento de su
implementacin de hilos, si usted est utilizando un multiprocessor y sobre la carga prevista en su
recurso. En particular, si su recurso es en su mayora no en uso entonces los efectos de rendimiento
no ser un problema, y deberas adoptar el estilo de codificacin ms simple. Pero a veces son
importantes, y slo debe ignorarlos despus de considerar explcitamente si estn obligados en su
situacin particular.
5.6. deadlock
Usted puede introducir los interbloqueos esperando sobre objetos, incluso aunque han cuidado de
tener un orden parcial en la adquisicin de las cerraduras. Por ejemplo, si tienes dos recursos
(llamada de ellos (1) y (2)), la siguiente secuencia de acciones produce un interbloqueo.
Hilo A adquiere recursos (1); Hilo B adquiere recursos (2); Hilo A quiere (2), as que se llama
"Monitor.Wait" para esperar (2); Hilo B quiere (1), as se llama "Monitor.Wait" para esperar (1).

Bloqueos como sta no son significativamente diferentes de las que hablamos en relacin con las
cerraduras. Usted debe arreglar que existe un orden parcial sobre los recursos gestionados con
variables de condicin, y que cada hilo deseen Introduccin a la programacin con C# hilos . 25

adquirir mltiples recursos hace segn este orden. As, por ejemplo, puedes decidir que (1) se
ordena antes (2). Luego hilo B no se permitira a tratar de adquirir (1) mientras sujeta (2), as que no
se producira el estancamiento.
Una interaccin entre bloqueos y esperando en los objetos es una fuente sutil de bloqueo.
Considere los siguientes dos mtodos (muy simplificados).
clase GG { static objeto un = new Object(); estticaB objeto = new Object(); static bool lista =
false;
public static void Get() { bloqueo (a) { bloqueo (b) { mientras (! listo) Monitor.Wait(b);}}}
public static void Give() { bloqueo (a) { bloqueo (b) {lista = true; Monitor.Pulse(b); } } }
} / / clase GG

Si "listo"es" falso "y del hilo de rosca A las llamadas"Haz", bloquear la llamada de"Monitor.Wait(b)".
Esto desbloquea "b", pero deja "un" bloqueado. Si el subproceso B llama "da", con la intencin de
causar una llamada de"Monitor.Pulse(b)", en lugar de ello bloquear tratando de bloquear" un", y su
programa habr un veredicto. Claramente, este ejemplo es trivial, desde la cerradura del " un" hace
no protege los datos (y la posibilidad de interbloqueo sea evidente), pero el general patrn ocurre.
Ms a menudo este problema ocurre cuando usted adquiere un bloqueo a nivel de una
abstraccin de su programa y luego llama a un nivel inferior, que bloquea (desconocido para el
nivel superior). Si este bloque puede ser liberado slo por un hilo que sostiene la cerradura de nivel
superior, usted ser deadlock. Es generalmente arriesgado poner en una abstraccin de nivel
inferior manteniendo uno de sus cerraduras, a menos que entiendes plenamente las circunstancias
bajo las cuales podra bloquear el mtodo llamado. Una solucin aqu es explcitamente
desbloquear la cerradura nivel superior antes de llamar a la abstraccin de nivel inferior, como
hemos hablado anteriormente; Pero como ya comentamos, esta solucin tiene sus propios peligros.
Una mejor solucin es organizar para poner fin a la " cerradura" declaracin antes de llamar a. Puede
encontrar ms discusiones sobre este problema, conocido como el "problema de monitor anidados",
en la literatura [8]. 26 . Introduccin a la programacin con C# hilos

6. USANDO HILOS: TRABAJANDO EN PARALELO


Como ya comentamos anteriormente, existen varias clases de situaciones donde usted querr un
subproceso independiente de la bifurcacin: utilizar un multiprocessor; para hacer trabajo til
mientras esperan un dispositivo lento; para satisfacer a los usuarios humanos trabajando en varias
acciones a la vez; proporcionar servicio de red a mltiples clientes simultneamente; y aplazar
hasta un tiempo menos ocupado.
Es muy comn encontrar programas de aplicacin sencilla utilizando varios hilos. Por ejemplo,
tal vez tengas un hilo haciendo su cmputo principal, un segundo hilo escribir alguna salida en un
archivo, un tercer hilo esperando (o respondiendo a) entrada de usuario interactiva y un cuarto hilo
ejecutando en segundo plano para limpiar sus estructuras de datos (por ejemplo, rebalancing un
rbol). Tambin es muy probable que paquetes de bibliotecas que utilizas generar internamente
sus propias roscas.
Cuando estn programando con hilos, que generalmente los dispositivos lento impulsin a
travs de las llamadas sincrnicas biblioteca que suspensin el subproceso de llamada hasta la
accin del dispositivo completa, pero permite que otros subprocesos en su programa para
continuar. Usted no encontrar necesidad de utilizar ms viejos esquemas para operacin
asincrnica (como rutinas de terminacin de entrada-salida). Si no quieres esperar por el resultado
de una interaccin del dispositivo, invocarlo en un subproceso independiente. Si quieres tener
simultneamente mltiples solicitudes de dispositivo excepcional, invocarlas en varios
subprocesos. En general las bibliotecas proporcionadas con el entorno C# proporcionan llamadas
sincrnicas apropiadas para la mayora de los propsitos. Descubrirs que las bibliotecas legadas
no hagan (por ejemplo, cuando el programa C# es llamar a objetos COM); en esos casos, es
generalmente una buena idea aadir una capa proporciona un paradigma llamado sincrnico, para
que el resto de su programa puede ser escrito en un estilo natural threadbased.
6.1. usando hilos en Interfaces de usuario
Si su programa est interactuando con un usuario humano, generalmente querr que sea sensible
incluso mientras se est trabajando en una solicitud. Esto es particularmente cierto de interfaces
windoworiented. Si su pantalla interactiva va tonto es particularmente irritante para el usuario
(por ejemplo, windows no repintan o las barras de desplazamiento no desplazarse) slo porque una
consulta de base de datos est tomando mucho tiempo. Usted puede alcanzar respuesta mediante el
uso de hilos extras
En Windows Forms de C# la maquinaria de que su programa oye acerca de eventos de la
interfaz de usuario al registrarse delegados como eventhandlers para los diversos controles.
Cuando ocurre un evento, el control llama el eventhandler apropiado. Pero el delegado se llama
sncrono: hasta que vuelve, no hay ms eventos sern reportados al programa, y esa parte de la
pantalla del usuario aparecer congelada. As que usted debe decidir si la accin solicitada es lo
suficientemente corta para que puedes hacerlo con seguridad sincrnicamente, o si deberas hacer
la obra en un subproceso independiente. Una buena regla general es que si la eventhandler puede
terminar en un perodo de tiempo que no es importante para un humano (digamos, 30
milisegundos) entonces puede funcionar sincrnicamente. En los dems casos, el controlador de
eventos slo debe extraer los datos de parmetros apropiados desde el estado de la interfaz de

usuario (por ejemplo, el contenido de las cajas de texto o los botones de radio) y solicite un
subproceso asincrnico hacer el trabajo. Introduccin a la programacin con C# hilos . 27

En la fabricacin de este juicio llamar necesitas considerar el peor caso retraso que pudiera
incurrir el cdigo.
Cuando usted decide mover el trabajo provocada por un evento de la interfaz de usuario en un
subproceso independiente, tienes que tener cuidado. Sncrono, debe capturar una visin consistente
de las partes pertinentes de la interfaz de usuario en el caso de controlador delegado, antes de
transferir el trabajo para el subproceso de trabajo asincrnico. Tambin debes tener cuidado que el
subproceso de trabajo se desista si se convierte en irrelevante (por ejemplo, el usuario hace clic en
"Cancelar"). En algunas aplicaciones debe serializar correctamente para que el trabajo se hace en el
orden correcto. Finalmente, debes tener cuidado en la actualizacin de la interfaz de usuario con
resultados del trabajador. No es legal que un subproceso arbitrario modificar el estado de la
interfaz de usuario. Por el contrario, debe utilizar el subproceso de trabajo el " Invoke" mtodo de un
control para modificar su estado. Esto es porque los diversos objetos instancia de control no son
threadsafe: sus mtodos no pueden ser llamados simultneamente. Dos tcnicas generales pueden
ser tiles. Uno es mantener exactamente un subproceso de trabajo y organizar sus controladores de
eventos para alimentarla peticiones a travs de una cola de ese programa explcitamente. Una
alternativa es crear subprocesos de trabajo segn sea necesario, tal vez con nmeros de secuencia en
sus peticiones (generados por sus controladores de eventos).
Puede ser difcil cancelar una accin que procede en un subproceso de trabajo asincrnico. En
algunos casos es conveniente utilizar la "Thread.Interrupt" mecanismo (discutido ms adelante). En
otros casos es muy difcil hacerlo correctamente. En estos casos, considere poniendo una bandera
para registrar la cancelacin, y luego comprobar esa bandera antes de que el subproceso de trabajo
hace algo con sus resultados. Un subproceso de trabajo cancelado luego silenciosamente puede
morir si se ha vuelto irrelevante a los deseos del usuario. En todos los casos de cancelacin,
recuerda que no es necesario hacer todos los SANEAMIETNO sncrono con la solicitud de
cancelacin. Todo lo que necesita es que despus de responder a la solicitud de cancelacin, el
usuario nunca ver nada de lo que resulta de la actividad cancelada.
6.2. con hilos en servidores de red
Servidores de red generalmente deben servir a mltiples clientes simultneamente. Si su red de
comunicacin se basa en RPC [3], esto suceder sin ningn trabajo por su parte, desde el lado del
servidor de su sistema RPC invocar a cada llamada entrante concurrente en un subproceso
independiente, por un nmero adecuado de hilos internamente para su implementacin se
bifurcan. Pero varios subprocesos se puede utilizar incluso con otros paradigmas de comunicacin.
Por ejemplo, en un protocolo de connectionoriented tradicional (por ejemplo, transferencia de
archivos en capas encima de TCP), probablemente debera tenedor un hilo para cada conexin
entrante. Por el contrario, si escribes un programa cliente y no quieres esperar la respuesta de un
servidor de red, invocar el servidor desde un subproceso independiente.
6.3. aplazar trabajo
La tcnica de la adicin de hilos de rosca con el fin de aplazar el trabajo es muy valiosa. Hay varias
variantes del esquema. El ms simple es que tan pronto como su mtodo ha trabajado bastante para
calcular su resultado, horquilla un hilo para hacer el resto de la obra y luego volver a tu
identificador de llamadas en el hilo original. Esto 28 . Introduccin a la programacin con C# hilos

reduce la latencia de la llamada al mtodo (el tiempo transcurrido desde ser llamado a devolver),
con la esperanza de que el trabajo diferido puede hacerse ms barato ms tarde (por ejemplo,
porque un procesador pasa inactivo). La desventaja de este enfoque ms simple es que podra crear
grandes cantidades de hilos, e incurre en el costo de la llamada "horquilla" cada vez. A menudo, es
preferible mantener un hilo de limpieza solo y pide que lo alimentan. Es incluso mejor cuando el
ama de llaves doesn't necesita alguna informacin de los hilos principales, ms all del hecho de
que hay trabajo por hacer. Por ejemplo, esto ser cierto cuando el ama de llaves es responsable de
mantener una estructura de datos en una forma ptima, aunque los hilos principales todava tendr
la respuesta correcta sin esta optimizacin. Una tcnica adicional aqu es programar el ama de
llaves para fusionar las peticiones similares en una sola accin, o limitarse a ejecutar no ms a
menudo que un intervalo peridico elegido.
6.4. canalizacin
En un multiprocessor, hay un uso especializado de subprocesos adicionales que es particularmente
valioso. Usted puede construir una cadena de relaciones producerconsumer, conocido como una
tubera. Por ejemplo, cuando inicia hilo A una accin, todo lo que hace es enqueue una solicitud en
un bfer. Hilo B toma la accin desde el buffer, realiza parte del trabajo, entonces cola en un bfer
de segundo. Hilo C toma a partir de ah y hace el resto de la obra. Esto forma una tubera de three
stage. Los tres hilos funcionan en paralelo, excepto cuando sincroniza para acceder a los buffers, as
que este gasoducto es capaz de utilizar hasta tres procesadores. En su mejor momento, canalizacin
puede alcanzar casi lineal speedup y puede aprovechar un multiprocessor. Un oleoducto tambin
puede ser til en un uniprocessor si cada subproceso encontrar algunos retrasos realtime (tales
como errores de pgina, manejo del dispositivo o red de comunicaciones).
Por ejemplo, el siguiente fragmento de programa utiliza una tubera simple de tres etapas. El
"cola" clase implementa una cola FIFO sencilla, utilizando una lista enlazada. Se inicia una accin en
la tubera mediante una llamada a la "PaintChar" mtodo de una instancia de la "PipelinedRasterizer"
clase. Un subproceso auxiliar se ejecuta en "rasterizador"y otro en"pintor". Estos hilos se comunican a
travs de las instancias de la "cola" clase. Tenga en cuenta esa sincronizacin para
"QueueElem"objetos se consigue mediante la celebracin de la correspondiente" cola de" bloqueo del
objeto.
clase QueueElem {/ / sincronizado por cerradura pblico objeto v; la cola / / inmutable pblico
QueueElem siguiente = null; / / protegido por bloqueo de cola
pblico QueueElem (objeto v) { este.v = v;}
} / / clase QueueElem Introduccin a la programacin con C# hilos . 29

clase Cola {cabeza QueueElem = null; / / protegido por "esta" cola de QueueElem = null; / /
protegido por "esto"
public void Enqueue (objeto v) {/ / Append "v" a esta cola cerradura (esto) {QueueElem e =
new QueueElem(v); si(cabeza == null) {cabeza = e; Monitor.PulseAll(this); } ms {tail.next = e;}
cola = e; } }
pblico Objeto Dequeue() {/ / quitar el primer elemento de res objeto cola = null;
cerradura(esto) { mientras (cabeza == null) Monitor.Wait(this); res = head.v; cabeza =
head.next;} res de retorno ; }
} / / clase cola
clase PipelinedRasterizer {rasterizeQ cola = new Queue(); Cola paintQ = new Queue(); Hilo de
rosca t1, t2; F fuente; Pantalla d;
public void PaintChar(char c) {rasterizeQ.Enqueue(c)};
vaco Rasterizer() { mientras (true) { char c = (char) (rasterizeQ.Dequeue()); / / convertir
caracteres a un mapa de bits... Mapa de bits, b = f.Render(c); paintQ.Enqueue(b); }} 30 .
Introduccin a la programacin con C# hilos

vaco Painter() { mientras (true) {Bitmap b = (Bitmap)(paintQ.Dequeue()); / / pintura de mapa de


bits en el dispositivo de grficos... d.PaintBitmap(b);}}
pblico PipelinedRasterizer (Font f, pantalla d) {this.f = f; this.d = d; t1 = nuevo hilo (nuevo
ThreadStart (esto.Rasterizador)); T1.Start(); T2 = nuevo hilo (nuevo ThreadStart (esto.Pintor));
T2.Start(); }} / / clase PipelinedRasterizer

Hay dos problemas con canalizacin. Primero, tienes que ser cuidadoso acerca de cunto del trabajo
obtiene en cada etapa. Lo ideal es que las etapas son iguales: Esto proporcionar el mximo
rendimiento, utilizando plenamente todos sus procesadores. Lograr este ideal requiere mano
tuning y retuning como los cambios en el programa. En segundo lugar, el nmero de etapas en su
tubera estticamente determina la cantidad de concurrencia. Si sabes cmo muchos procesadores, y
exactamente donde se producen las demoras realtime, esto va a estar bien. Para ambientes ms
flexibles o porttiles puede ser un problema. A pesar de estos problemas, la segmentacin es una
tcnica poderosa que tiene aplicabilidad amplia.
6.5. el impacto de su entorno
El diseo de su sistema operativo y bibliotecas de ejecucin afectar la medida que es deseable o
til a las roscas de la horquilla. Las bibliotecas que se utilizan comnmente con C# son
razonablemente threadfriendly. Por ejemplo, incluyen entrada sincrnica y mtodos de produccin
que suspensin slo el subproceso de la llamada, no todo el programa. La mayora de las clases de
objeto cuentan con documentacin decir hasta qu punto es seguro llamar a mtodos
simultneamente desde varios subprocesos. Usted necesita tener en cuenta, sin embargo, que
muchas de las clases de especifican que sus mtodos estticos son threadsafe, y sus mtodos de
instancia no son. Para llamar a los mtodos de instancia debe puede utilizar su propio bloqueo para
asegurarse de slo un hilo a la vez est llamando, o en muchos casos la clase proporciona un
mtodo "Sincronizada" que va a crear un contenedor de sincronizacin alrededor de una instancia
de objeto.
Necesitar conocer algunos de los parmetros de rendimiento de su implementacin de hilos.
Cul es el coste de crear un hilo? Cul es el costo de mantener un subproceso bloqueado en
existencia? Cul es el costo de un cambio de contexto? Cul es el costo de un " cerradura"
declaracin cuando el objeto es no bloqueado? Sabiendo esto, usted ser capaz de decidir en qu
medida es factible o til para aadir subprocesos adicionales a su programa. Introduccin a la
programacin con C# hilos . 31

6.6. posibles problemas con la adicin de hilos de rosca


Necesitas un poco de cuidado en la adicin de hilos de rosca del ejercicio, o usted encontrar que su
programa se ejecuta ms despacio en lugar de ms rpido.
Si tienes significativamente ms subprocesos listos para ejecutarse que hay procesadores,
generalmente encontrar que el rendimiento de su programa se degrada. Esto es en parte porque la
mayora programadores del hilo de rosca son muy lentos en tomar decisiones rescheduling
general. Si hay un procesador inactivo esperando su hilo, el programador puede probablemente
llegarlo bastante rpido. Pero si el hilo tiene que ser puesto en una cola y despus cambi a un
procesador en lugar de algn otro hilo, ser ms caro. Un segundo efecto es que si tienes un
montn de threads ejecutndose son ms propensos al conflicto sobre las cerraduras o los recursos
gestionados por sus variables de condicin.
Sobre todo, cuando aades hilos para mejorar la estructura del programa (por ejemplo manejar
dispositivos lentos o rpidamente, o para las invocaciones de RPC en respuesta a eventos de la
interfaz de usuario) no encontrar este problema; Pero cuando aades hilos para propsitos de
rendimiento (por ejemplo, realizar varias acciones en paralelo, o aplazar la obra o utilizando multi
processors), usted necesitar preocuparse si usted sobrecarga el sistema.
Pero quiero destacar que esta advertencia se aplica slo a las roscas que estn dispuestas a
correr. El gasto de tener hilos bloqueados esperando en un objeto es generalmente menos
significativo, siendo slo la memoria utilizada para las estructuras de datos del planificador y la
pila del subproceso. Wellwritten multithreaded aplicaciones suelen tienen un gran nmero de
subprocesos bloqueados (50 no es infrecuente).
En la mayora de los sistemas, las instalaciones de creacin y terminacin de hilo no son
baratas. La implementacin de hilos se encargar probablemente para guardar en cach unos
cadveres de hilo terminada, para que usted no paga por creacin de pila en cada encrucijada, pero
sin embargo creando un nuevo hilo probablemente tendrn un costo total de dos o tres decisiones
rescheduling. As que usted no debera horquilla demasiado pequeo un cmputo en un
subproceso independiente. Una medida til de una implementacin de hilos en un multiprocessor
es el cmputo ms pequeo por lo que es rentable a un hilo de la bifurcacin.
A pesar de estas precauciones, tenga en cuenta que mi experiencia ha sido que los
programadores son ms propensos a errar creando demasiados pocos hilos como creando
demasiados.

7. USO DE INTERRUPCIN: DESVIAR EL FLUJO DE CONTROL


El propsito del mtodo "Interrupcin" de un hilo es decir el hilo que debe abandonar lo que est
haciendo y que control de volver a una abstraccin de nivel superior, probablemente el nico que
hizo la llamada de "Interrumpir". Por ejemplo, en un multiprocessor podra ser til para varios
competidores algoritmos para resolver el mismo problema de la bifurcacin, y cuando termina el
primero de ellos anula los dems. O usted puede embarcarse en un cmputo largo (por ejemplo,
una consulta a un servidor de base de datos remota), pero anularlo si el usuario hace clic en un
Cancelar botn. O tal vez quieras limpiar un objeto que utiliza algunos hilos demonio internamente.
Por ejemplo, podramos aadir un "Disponer"mtodo"PipelinedRasterizer"para terminar sus dos
hilos cuando hayamos terminado de usar el"PipelinedRasterizer"32 . Introduccin a la programacin con
C# hilos

* El recolector de basura podra notar que si la nica referencia a un objeto es de hilos que no son
accesibles desde el exterior y que estn bloqueados en una espera con ningn tiempo de espera, entonces el
objeto y los hilos pueden desecharse. Lamentablemente, recolectores de basura reales no inteligentes.

objeto . Observe que si no hacemos esto el "PipelinedRasterizer" objeto no se nunca recogern


la basura, porque se hace referencia a sus propias roscas daimonion. *
clase PipelinedRasterizer: IDisposable {
public void Dispose() { cerradura(este) { si (t1! = null) t1.Interrupt(); si(t2! = null) t2.Interrupt();
T1 = null; T2 = null; } }
vaco Rasterizer() { pruebe { mientras (true) { char c = (char) (rasterizeQ.Dequeue()); / /
convertir caracteres a un mapa de bits... Mapa de bits, b = f.Render(c); paintQ.Enqueue(b); {}}
catch (ThreadInterruptedException) {}}
vaco Painter() { pruebe { mientras (true) {Bitmap b = (Bitmap)(paintQ.Dequeue()); / / pintura de
mapa de bits en el dispositivo de grficos... d.PaintBitmap(b);}}
atrapar(ThreadInterruptedException) { } }
} clase PipelineRasterizer

Hay veces cuando quiera interrumpir un subproceso que se est realizando un clculo largo pero
no llamadas de "Espera". La documentacin de C# es un poco vaga acerca de cmo hacer esto, pero
seguramente puede conseguir este efecto si el cmputo largo ocasionalmente llama
"Thread.Sleep(0)". Diseos anteriores tales como Java y Modula incluyen mecanismos diseados
especficamente para permitir que un subproceso sondear a Una introduccin a la programacin con C#
hilos . 33

ver si hay una interrupcin pendiente (por ejemplo, si una llamada de "espera" lanzara el
"interrumpido" excepcin).
Modula tambin permite dos tipos de espera: se y nonalertable. Esto permiti un espacio de su
programa a escribirse sin preocupacin por la posibilidad de una excepcin repentina que surjan.
En C# todas llamadas de "Monitor.Wait" son interrumpible (como son las llamadas correspondientes
en Java), y para ser correcta debe tampoco arreglan eso todas llamadas de " espera" estn preparados
para el "interrumpido" ser excepcin, o usted debe verificar que no se llamar al mtodo de
interrupcin en las roscas que estn realizando los espera. Esto no debera ser demasiado de una
imposicin en su programa, puesto que ya necesitas restaurar invariantes monitor antes de llamar
"espera". Sin embargo tienes que tener cuidado que si se produce la excepcin "Interrumpido" luego
libere recursos retenidos en la pila de Marcos ser desenrollado, presumiblemente por escrito
correspondiente "finalmente" declaraciones.
El problema con hilo de interrupciones es que son, por su propia naturaleza, intrusivo.
Usndolos tender a hacer su programa que menos bien estructurado. Un flujo de straightforward
looking de control en un subproceso de repente puede ser desviado a causa de una accin iniciada
por otro subproceso. Este es otro ejemplo de una instalacin que hace ms difcil verificar la
exactitud de un pedazo de programa por la inspeccin de local. A menos que las alertas se utilizan
con mucha moderacin, harn que tu programa ilegible, insostenible y quizs incorrecto. Te
recomiendo utilizar pocas interrupciones y que el "interrumpir" mtodo debera llamarse solamente
de la abstraccin donde se cre el hilo. Por ejemplo, un paquete no debe interrumpir hilo de un
oyente que le pasa a estar ejecutando dentro del paquete. Este Convenio le permite ver una
interrupcin como una indicacin de que el hilo debe terminar completamente, pero limpio.
a menudo hay mejores alternativas al uso de interrupciones. Si sabes qu objeto est esperando
un hilo, ms simplemente puede prod estableciendo una bandera booleana y llamando
"Monitor.Pulse". Un paquete podra proporcionar puntos de entrada adicional cuyo propsito es
prod un subproceso bloqueado dentro del paquete en una espera de largo plazo. Por ejemplo, en
lugar de implementar "PipelinedRasterizer.Dispose" con el "interrumpir" mecanismo nos podramos
haber aadido un "disponer" mtodo para el "cola" de la clase y que.
Las interrupciones son ms tiles cuando no sabes exactamente lo que est pasando. Por
ejemplo, el subproceso de destino podra ser bloqueado en cualquiera de varios paquetes, o dentro
de un solo paquete podra estar esperando en cualquiera de los varios objetos. En estos casos una
interrupcin es sin duda la mejor solucin. Aun cuando existen otras alternativas disponibles, sera
mejor utilizar interrupciones slo porque son un solo esquema unificado para provocar la
terminacin de subprocesos.
No hay que confundir "Interrumpir" con el mecanismo absolutamente distinto llamado
"Abortar", que describir ms adelante. Slo "interrumpir" permite interrumpir el hilo en un lugar
welldefined, y es la nica manera que el hilo tendr alguna esperanza de la restauracin de los
invariantes en sus variables compartidas.

8. OTRAS TCNICAS
La mayora de los paradigmas de programacin para el uso de hilos de rosca es muy simple. Varios
de ellos he descrito anteriormente; usted descubrir muchos otros como usted gana 34 . Introduccin
a la programacin con C# hilos

experiencia. Algunas de las tcnicas tiles son mucho menos obvias. Esta seccin describe algunos
de estos menos obvios.
8.1.-llamadas
La mayor parte del tiempo la mayora de los programadores construyen sus programas usando
capas de abstracciones. Abstracciones de nivel superiores llaman slo baja los y abstracciones en el
mismo nivel no llamarnos. Todas las acciones se inician en el nivel superior.
Esta metodologa lleva bastante bien a un mundo con concurrencia. Usted puede arreglar que
cada subproceso honrar los lmites de la abstraccin. Hilos daimonion permanente dentro de una
abstraccin inician llamadas a niveles inferiores, pero no a niveles ms altos. Las capas de
abstraccin tiene el beneficio aadido que forma un orden parcial, y este orden es suficiente para
evitar los interbloqueos cuando bloquee objetos, sin ningn cuidado adicional del programador.
Este topdown puramente capas no es satisfactorio cuando las acciones que afectan las
abstracciones highlevel pueden iniciarse en una capa baja en su sistema. Un ejemplo frecuente de
esto es en el lado receptor de comunicaciones de la red. Otros ejemplos son introducidos por el
usuario, y cambios de estado espontneo en los dispositivos perifricos.
Considere el ejemplo de una trata de paquete de comunicaciones de paquetes entrantes de una
red. Aqu hay tpicamente tres o ms capas de envo (correspondientes a las capas de enlace, red y
transporte de datos de terminologa OSI). Si se intenta mantener una topdown llamada jerarqua,
usted encontrar que usted incurrir en un cambio de contexto en cada una de estas capas. El hilo
que desea recibir informacin de su conexin de capa de transporte no puede ser el hilo que enva
un paquete entrante de Ethernet, ya que el paquete Ethernet podra pertenecer a una conexin
diferente, o un protocolo diferente (por ejemplo, UDP en lugar de TCP) o una en familia conjunto
protocolo diferente (por ejemplo, DECnet en lugar de IP). Los implementadores muchos han
tratado de mantener esta estratificacin para la recepcin de paquetes, y el efecto ha sido
uniformemente mala actuacin dominado por el costo de cambios de contexto.
La tcnica alternativa es conocida como "upcalls" [6]. En esta metodologa, mantener una piscina
de hilos dispuestos a recibir paquetes de capa (por ejemplo, Ethernet) de enlace de datos entrantes.
El hilo receptor enva el tipo de protocolo de Ethernet y llama a la capa de red (por ejemplo, DECnet
o IP), donde despacha otra vez y llama a la capa de transporte (por ejemplo, TCP), donde hay un
mensaje final a la conexin apropiada. En algunos sistemas, este paradigma de upcall se extiende
en la aplicacin. La atraccin es de alto rendimiento: hay no hay cambios de contexto innecesarios.
Usted paga para esta actuacin. Como de costumbre, la tarea del programador se ha hecho ms
complicada. En parte esto es porque cada capa tiene ahora una interfaz upcall, as como la interfaz
tradicional downcall. Pero tambin el problema de sincronizacin se ha vuelto ms delicado. En un
sistema puramente topdown est bien sostener la cerradura de una capa mientras que llamando
una capa ms baja (a menos que la capa ms baja podra bloquear un objeto esperando alguna
condicin para convertirse en verdadero y causar as el tipo de bloqueo de monitor anidados que
discutimos anteriormente). Pero cuando usted hace una upcall fcilmente puede provocar un
estancamiento que involucra slo el Introduccin a la programacin con C# hilos . 35

cerraduras si un hilo de upcalling mantiene un bloqueo de nivel inferior necesita adquirir


un bloqueo en una abstraccin de nivel superior (desde el bloqueo podra ser celebrado por algn
otro hilo que est haciendo un downcall). En otras palabras, la presencia de upcalls hace ms
probable que usted viola la regla de orden parcial para el bloqueo de objetos. Para evitar esto, debe
evitar generalmente mantienen un bloqueo mientras haciendo un upcall (pero esto es ms fcil
decirlo que hacerlo).
8.2. versin sellos y almacenamiento en cach
A veces simultaneidad puede hacerlo ms difcil utilizar la informacin almacenada en cach. Esto
puede ocurrir cuando un subproceso independiente ejecutando en un nivel inferior en su sistema
invalida alguna informacin a un hilo que se est ejecutando actualmente en un nivel superior. Por
ejemplo, puede cambiar la informacin sobre un volumen de disco ya sea por problemas de
hardware o porque el volumen ha sido eliminado y reemplazado. Puede utilizar upcalls para
invalidar las estructuras de la memoria cach en el nivel superior, pero esto no invalida estado
localmente por un hilo. En el ejemplo ms extremo, un subproceso puede obtener informacin de
un cach y estar a punto de llamar a una operacin en el nivel inferior. Entre el momento en que la
informacin proviene de la cach y el momento en que la llamada se produce en realidad, la
informacin podra haberse convertido en no vlida.
Una tcnica conocida como "sellos versin" puede ser til aqu. En la abstraccin de bajo nivel
que mantener un contador asociado con los datos verdaderos. Cuando cambian los datos, se
incrementa el contador. (Suponga que el contador es tan grande que nunca desbordamiento).
Cuando se expida una copia de algunos de los datos a un nivel superior, es acompaado por el
valor actual del contador. Si el cdigo de nivel superior es almacenar en cach los datos, almacena
en cach el valor del contador asociado demasiado. Cuando haga una llamada a nivel inferior, y la
llamada o sus parmetros dependen de datos previamente obtenidos, se incluye el valor asociado
del contador. Cuando el nivel bajo recibe dicha llamada, compara el valor entrante del contador con
el valor actual de la verdad. Si son diferentes devuelve una excepcin para el nivel superior, que
entonces sabe que reconsider su llamada. (A veces, puede proporcionar los datos nuevos con la
excepcin). Por cierto, esta tcnica tambin es til cuando se mantienen en la memoria cach de
datos a travs de un sistema distribuido.
8.3. trabajo equipos (subprocesos)
Hay situaciones que son mejor descritas como "una vergenza de paralelismo", cuando usted
puede estructurar su programa a tener muchsimo ms concurrencia que pueden ser eficientemente
acomodados en su mquina. Por ejemplo, un compilador implementado usando concurrencia
estaran dispuesto a utilizar un subproceso independiente para compilar cada mtodo, o incluso
cada declaracin. En tales situaciones, si se crea un subproceso para cada accin terminar con
tantos hilos que el programador se convierte absolutamente ineficaz, o tanta que tiene numerosos
conflictos de bloqueo, o tantos que ejecute fuera de memoria para las pilas.
Tu eleccin aqu es ms restringida en la bifurcacin, o usar una abstraccin que controlar su
bifurcacin para ti. Tan una abstraccin primero fue descrita en papel Vandevoorde y Roberts [20]
y est disponible para programadores de C# a travs de los mtodos de la " ThreadPool" clase: 36 .
Introduccin a la programacin con C# hilos

pblico clase sellada ThreadPool {...}

La idea bsica es enqueue solicitudes para actividad asincrnica y tener un grupo fijo de hilos que
realizan las peticiones. La complejidad viene en gestionar las peticiones, sincronizacin entre ellos y
la coordinacin de los resultados.
Cuidado, sin embargo, que la C# "ThreadPool"clase utiliza mtodos completamente estticos
hay una nica agrupacin de hilos para toda la aplicacin. Esto est bien si las tareas que le das al
grupo de subprocesos son puramente computacionales; Pero si las tareas pueden incurrir en
retrasos (por ejemplo, haciendo una llamada RPC de red) bien podra encontrar la abstraccin
Shagun inadecuada.
Es una propuesta alternativa, que no he visto an en la prctica, para implementar
"Thread.Create"y"Thread.Start"de tal manera que ellos diferir en realidad creando el nuevo
subproceso hasta que haya un procesador disponible para ejecutarlo. Esta propuesta se ha llamado
"vagos que se bifurcan".
8.4. superposicin de cerraduras
Los siguientes son momentos cuando puede usar ms de una cerradura para algunos datos.
A veces cuando es importante permitir concurrente acceso de lectura a algunos datos, mientras
que sigue usando la exclusin mutua para acceso de escritura, bastar con una tcnica muy sencilla.
Usar bloqueos dos (o ms), con la regla de que ningn hilo sosteniendo una cerradura puede leer
los datos, pero si un subproceso quiere modificar los datos debe adquirir las cerraduras ambos (o
todos).
Otra tcnica de bloqueo superpuestas se utiliza a menudo para recorrer una lista enlazada. Una
cerradura para cada elemento y adquirir la cerradura para el elemento siguiente antes de soltar el
uno para el elemento actual. Esto requiere el uso explcito de la " Enter"y"salida" mtodos, pero
puede producir mejoras en el rendimiento espectacular reduciendo conflictos bloqueo.

9. AVANZADO C# CARACTERSTICAS
a lo largo de este trabajo he restringido la discusin a un pequeo subconjunto de los
"System.Threading"espacio de nombres. Recomiendo encarecidamente que restringe su
programacin a este subconjunto (adems de la "System.Threading.ReaderWriterLock" clase) tanto
como puedas. Sin embargo, el resto del espacio de nombres fue definido para un propsito, y hay
veces cuando usted necesitar utilizar partes de l. Esta seccin describe las otras caractersticas.
Hay variantes del "Monitor.Wait"toma un argumento adicional. Este argumento especifica un
intervalo de tiempo de espera: Si el hilo no es despertado por "pulso","PulseAll"o"interrumpir"dentro
de ese intervalo, entonces la llamada"espera" retorna de todos modos. En tal situacin la llamada
devuelve "falso".
Hay una manera alternativa para notificar a un subproceso que debe desistir: llamas de la rosca
"Abortar"mtodo de. Esto es mucho ms drstico y disruptiva que " interrumpir", porque se produce
una excepcin en un punto arbitrario y illdefined (en vez de slo en llamadas de
"esperar","dormir"o"nete"). Esto significa que en general ser imposible para el hilo restaurar
invariantes. Dejar su compartida Introduccin a la programacin con C# hilos . 37

datos de en Estados illdefined. El uso razonable solamente de " abortar" es terminar un


cmputo ilimitado o un noninterruptible espera. Si usted tiene que recurrir a " abortar" tendr que
tomar medidas para reinitialize o descarte afectado compartida variables.
Varias clases en "System.Threading"corresponden a objetos implementados por el ncleo. Estos
incluyen "AutoResetEvent","ManualResetEvent","Mutex", y "WaitHandle". El beneficio real slo que
obtendr del uso de estos es que pueden utilizarse para sincronizar entre subprocesos en mltiples
espacios de direcciones. Tambin habr momentos cuando necesitas sincronizar con cdigo
heredado.
El "Enclavijado"clase puede ser til para operaciones simples de incremento, decremento o
intercambio atmicas. Recuerda que solo puedes hacerlo si su invariante implica slo una sola
variable. "Interlocked" no te ayudar cuando participa ms de una variable.
10. EL PROGRAMA DE CONSTRUCCIN
Un programa exitoso debe ser til, correcta, vivo (como se define a continuacin) y eficiente. El uso
de simultaneidad puede afectar a cada uno de ellos. He discutido muchas tcnicas en las secciones
anteriores que te ayudar. Pero, cmo sabr si han tenido xito? La respuesta no es clara, pero en
esta seccin puede ayudarle a descubrirlo.
El lugar donde simultaneidad puede afectar la utilidad est en el diseo de las interfaces para
paquetes de bibliotecas. Se deben disear sus clases con la suposicin de que las llamadas utilizarn
varios subprocesos. Esto significa que debe asegurarse de que todos los mtodos son reentrant del
hilo de rosca (es decir, pueden ser llamados por varios subprocesos simultneamente), incluso si
esto significa que cada mtodo inmediatamente adquiere un bloqueo compartido solo. Usted no
debe devolver resultados en variables estticas compartidas, ni en almacenamiento compartido
asignado. Los mtodos deben ser sincrnicos, no regresar hasta que los resultados estn disponibles
si tu llamador quiere hacer otros trabajos mientras tanto, puede hacerlo en otros subprocesos.
Incluso si usted no tiene actualmente ningn cliente de multithreaded para una clase, le
recomiendo que siga estas pautas para que evitar problemas en el futuro.
No todos estn de acuerdo con el prrafo anterior. En particular, la mayora de los mtodos de
instancia en las bibliotecas con C# (aquellos en el CLR y la plataforma .net SDK) no es threadsafe.
Asumen que una instancia del objeto se llama desde slo un hilo a la vez. Algunas de las clases
proporcionan un mtodo que devolver una instancia de objeto correctamente sincronizada, pero
muchos no lo hacen. El motivo de esta decisin de diseo que es el costo de la " cerradura"
declaracin se crea que era demasiado alto como para usarlo donde sea necesario. Personalmente,
no estoy de acuerdo con esto: los gastos de envo un programa incorrectamente sincronizado
pueden ser mucho mayor. En mi opinin, es la solucin correcta implementar la " cerradura"
declaracin de una manera que es suficientemente barata. Se conocen las tcnicas para hacer este
[5].
Por "correcto" es decir que si su programa eventualmente produce una respuesta, ser el
definido por la especificacin. Su entorno de programacin es poco probable que proporcionan
mucha ayuda aqu ms all de lo que ya prev programas secuenciales. Sobre todo, debe ser
fastidioso de asociar cada dato con la cerradura de uno (y nico). Si no prestas atencin constante,
38 . Introduccin a la programacin con C# hilos

tu tarea ser intil. Si utiliza correctamente las cerraduras, y utilice siempre espera para objetos
en el estilo recomendado (retesting el valor booleano expresin despus de volver del " espera"),
entonces es poco probable equivocarse.
Por "vivir", es decir que su programa eventualmente producir una respuesta. Las alternativas
son ciclos infinitos o estancamiento. No puedo con ciclos infinitos. Creo que las notas de las
secciones anteriores le ayudar a evitar interbloqueos. Pero si fracasan y producir un interbloqueo,
debera ser bastante fcil de detectar.
Por "eficiente", es decir que el programa har buen uso de los recursos informticos disponibles
y por lo tanto producir su respuesta rpidamente. Otra vez, las sugerencias en las secciones
anteriores deberan ayudarte a evitar el problema de concurrencia afectando negativamente su
rendimiento. Y otra vez, su entorno de programacin para darle un poco de ayuda. Fallos de
funcionamiento son el ms insidioso de los problemas, ya que usted podra no incluso notar que
tienes. El tipo de informacin que necesita obtener incluye estadsticas sobre conflictos de bloqueo
(por ejemplo, con qu frecuencia subprocesos han tenido que bloquear con el fin de adquirir esta
cerradura, y cunto tiempo despus tuvieron que esperar para un objeto) y de los niveles de
concurrencia (por ejemplo, cul fue el nmero promedio de subprocesos listos para ejecutar en el
programa, o qu porcentaje del tiempo estaban listos "n" hilos).
En un mundo ideal, su entorno de programacin proporcionara un potente conjunto de
herramientas para ayudarle a lograr la correccin, liveness y eficiencia en el uso de simultaneidad.
Desafortunadamente en realidad lo ms que puedas encontrar hoy en da es las caractersticas
habituales de un depurador simblico. Es posible construir herramientas mucho ms potentes,
como especificacin de idiomas con comprobadores de modelo para verificar lo que el programa
hace [11], o herramientas que detectan acceder a variables sin las cerraduras apropiadas [19]. Hasta
ahora, estas herramientas no son ampliamente disponibles, aunque eso es algo que espero que sea
capaces de solucionar.
Una ltima advertencia: no enfatizar la eficiencia a expensas de correccin. Es mucho ms fcil
comenzar con un programa correcto y trabajar por lo que es eficiente, que comenzar con un
programa eficiente y el trabajo de hacer lo correcto.

11. OBSERVACIONES
Escribir programas concurrentes tiene una reputacin de ser exticos y difciles. Creo que no es ni.
Se necesita un sistema que le proporciona buenas primitivos y bibliotecas adecuadas, usted necesita
un cuidado bsico y esmero, necesitas un arsenal de tcnicas tiles y tienes que saber de los errores
comunes. Espero que este artculo te ha ayudado a compartir mi creencia.
Butler Lampson, Mike Schroeder, Bob Stewart y Bob Taylor me llev a escribir este artculo (en
1988), y Chuck Thackerme convenci a revisarlo para C# (en 2003).Si encontraron til, darles las
gracias.

S-ar putea să vă placă și