Documente Academic
Documente Profesional
Documente Cultură
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
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.
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.
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();
(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"
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".
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)); } }
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
Las interrupciones son complicadas, y su uso produce programas complicados. Los analizaremos
ms detalladamente en la seccin 7.
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
(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
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
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
* 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.
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.
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);
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
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
si
wQueue.Pulse() (doPulse);}
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
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
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
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
* 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.
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
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
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.