Índice
0. Introducción1. Problema
2. Solución
3. Discusión de la Solución
4. Práctica: Código C#
5. Artefactos
6. Conclusiones
7. Glosario
8. Literatura & Enlaces
0. Introducción
En recetas de la categoría C# ya hemos abordado el tema de sincronización de acceso concurrente a recursos compartidos por parte de múltiples threads; a diferencia de este caso, sobre esta receta multithreading vamos a tratar un caso particular en el que a través del uso de los miembros de la clase Monitor (System.Threading) podemos obtener el control de acceso a un recurso compartido, además de resaltar la situación particular en donde por razones lógicas en la implementación incorrecta se genera un bloqueo o deadlock sobre el recurso compartido.
1. Problema
Encontrar e implementar un método de bloqueo sincrónico para el control de acceso concurrente a un recurso compartido (e.g., archivo, conexión de red, modificación de datos en memoria).
2. Solución
La biblioteca base de clases de .NET cuentan con el mecanismo de control de acceso concurrente de múltiples threads por medio de la clase Monitor del nombre de espacio System.Threading.
3. Discusión de la Solución
La clase System.Threading.Monitor [2] provee el mecanismo de sincronización de acceso a recursos compartidos. Los miembros de esta clase permiten al programador tener mayor control de las áreas o regiones de código críticas para evitar intentos de modificación concurrentes por múltiples threads en ejecución que pudieran generar incoherencia en los resultados obtenidos o en el estado de un objeto en memoria.
Su uso comprende, en un principio, la siguiente sintaxis declarativa:
object locker = new object();
Monitor.Enter (locker);
try
{
// Conjunto de instrucciones sincrónicas
}
finally
{
Monitor.Exit (locker);
}
Aquí podemos destacar los siguientes elementos sintáticos:
- locker: instancia de Object para mantener el bloqueo en una región de código crítica.
- Monitor.Enter (locker): Invocación del método Enter [4] para iniciar el bloqueo sobre la región crítica.
- Bloque try-finally: A pesar de que no es requisito indispensable crear este bloque, si que resulta recomendable para controlar cualquier excepción que se pudiera generar en el area de región crítica.
- Monitor.Exit (locker): Liberación del recurso de la región crítica. Nótese que se debe pasar al método Exit [5] la instancia de bloqueo, locker en este caso.
Para adentrarnos en el uso básico de esta clase y sus miembros mencionados, recurramos a un ejemplo de uso:
Archivo C# AccesoArchivoConMonitor.cs [enlace alternativo]:
Con el ciclo for en las líneas 41-46 creamos hasta 5 threads que van intentar acceder al archivo (nombre-threads.txt) para agregar una línea de texto con su nombre (línea 44). Por cada iteración del ciclo se inicia la ejecución (línea 45) del thread recién instanciado. En la línea 9 creamos una instancia del objeto bloqueante para la sincronización que se efectúa dentro del método EscritirArchivo.
Dentro del método EscritirArchivo (líneas 11-37) se empieza con una línea que pausa el thread actual durante 1000 milisegundos (1 segundo) (línea 13). Obtenemos el nombre del thread en la línea 15. (Este nos va a servir para agregar la entrada de texto sobre el archivo.) En plena línea 20 invocamos al método Enter y le pasamos el objeto bloqueante declarado en la línea 9. (A partir de aquí empieza la sección crítica.) En el bloque try se abre/crea el archivo threads.txt con la clase StreamWriter [9] y se agrega el nombre y número de thread al archivo (línea 25).
Continuando, en el bloque finally se invoca al método Exit para desbloquear o finalizar la sección crítica y dar paso al siguiente thread localizado en la cola de espera.
Compilación:
- csc /target:exe AccesoArchivoConMonitor.cs
Ejecución assembly:
- .\AccesoArchivoConMonitor.exe
> Prueba de ejecución (local):
Figura 1. Ejecución assembly AccesoArchivoConMonitor.exe. |
Notemos que por cada ejecución el orden de ejecución varía, debido a la naturaleza estocástica de ejecución procesos paralelos.
Contenido del archivo threads.txt [enlace alternativo]:
[Nota: En la receta C# Sincronizar la Ejecución de Múltiples Threads usando un Monitor podrán encontrar una versión extendida de varios tópicos relacionados con el uso de la clase Monitor y varios ejemplos de uso. Recomendado.]
4. Práctica: Código C#
Adaptemos el ejemplo de uso de la clase Monitor y de la sentencia lock hallado en [1] para comprender y poner en práctica las situaciones en que se puede producir un bloqueo permanenente sobre una región crítica por un error semántico de parte del programador al intentar obtener el bloqueo sobre objetos ya en uso, y además, como vimos en la sección 3 recurrir al uso de los miembros de Monitor para hacer más natural el acceso sincrónico a un recurso compartido.
El método MetodoConBloqueoExtendido (líneas 57-66) simula un bloqueo de un región crítica durante un determinado tiempo. Por otro lado, en lo que se refiere a Main (líneas 12-54) ocurren las siguientes operaciones:
- Línea 17: Creación de instancia de Thread para la creación de un thread que encapsula el método MetodoConBloqueoExtendido.
- Línea 20: Inicio de la ejecución del método MetodoConBloqueoExtendido. En este caso se inicia un bloqueo sobre la instancia Object bloqueo1 a través de la referencia o1; se espera un segundo (1000 ms) y enseguida se genera un bloqueo con lock sobre la referencia o2, el cual se libera enseguida.
- Línea 23: quasi paralelamente sobre el thread Main se inicia un bloqueo sobre el objeto bloqueo2, se espera un segundo (línea 25) y dentro de la sentencia if (línea 28) se intenta realizar un bloqueo en un tiempo de espera de 5 segundos. Esta espera permite que se libere el bloqueo realizado sobre el método MetodoConBloqueoExtendido.
También hay que resaltar estas otras operaciones:
- Línea 40: Creación de nuevo thread que encapsula el método MetodoConBloqueoExtendido.
- Línea 43: Inicio de ejecución del thread recién creado. El método MetodoConBloqueoExtendido inicia su ejecución.
- Línea 45: Uso de lock con bloqueo2.
- Línea 49: Aquí es donde se manifiesta el problema de bloqueo permanente, debido a que se intenta obtener el control sobre la región crítica con el objeto bloqueo1 que se encuentra ya bloqueado desde MetodoConBloqueoExtendido. (El término anglosajón para referirse a un bloqueo de este tipo es precisamente deadlock). Otra forma en la que nos podemos referir a esta situación es de competencia por acceso a un recurso compartido mutuamente excluyente.
Compilación:
- csc /target:exe BloqueoRegionCritica.cs
Ejecución assembly:
- .\BloqueoRegionCritica.exe
5. Artefactos
Enlaces de descarga de los artefactos producidos a lo largo de la realización de esta receta:
6. Conclusiones
En esta receta multithreading se demostró uno de los errores semánticos más comunes que se comenten cuando se intenta acceder a una región crítica que está siendo controlada por un objeto de bloqueo. La competencia por recursos (deadlock) puede llevar a esta situación. Es aquí donde el programador debe prestar cuidadosa atención sobre el uso de la construcción lock y los miembros de la clase Monitor cuando se intente obtener el control sobre una región crítica. En la siguiente receta multithreading (la última de la serie del capítulo de los esenciales de threads) estudiaremos el manejo de excepciones sobre threads.
7. Glosario
- .NET
- BCL
- Biblioteca base de clases
- Deadlock
- Microsoft
- Multithreading
- Thread
8. Literatura & Enlaces
[1]: Multithreading in C# 5.0 Cookbook by Eugene Agafonov. Copyright 2013 Eugene Agafonov, 978-1-84969-764-4.[2]: Monitor Class (System.Threading) - http://msdn.microsoft.com/en-us/library/System.Threading.Monitor(v=vs.110).aspx
[3]: Receta C# No. 4-7: Sincronizar la Ejecución de Múltiples Threads usando un Monitor | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/07/receta-csharp-no-4-7-sincronizar-la-ejecucion-de-multiples-threads-usando-un-monitor.html
[4]: Monitor.Enter Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.monitor.enter(v=vs.110).aspx
[5]: Monitor.Exit Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.monitor.exit(v=vs.110).aspx
S
No hay comentarios:
Publicar un comentario
Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.