miércoles, 23 de julio de 2014

Receta C# No. 4-9: Sincronización de Múltiples Threads usando Mutex

Tabla de Contenido

0. Introducción
1. 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 C# previas aprendimos acerca del uso de dos métodos de sincronización de acceso a recursos compartidos (e.g., archivos, base de datos, conexioones de red): Monitor y Eventos. En esta ocasión vamos a sumar otro mecanismo de sincronización conocido como Mutex (o exclusión mutua). A diferencia de sus homólogos, con Mutex es posible sincronizar threads que se ejecuten en código no gestionado Win32. Aprenderemos acerca de su uso a través de tres ejemplos sencillos de sincronización de recursos compartidos.

1. Problema

Por consultas académicas a literatura computacional sabemos que existen otros mecanismos de sincronización además de Monitor y Eventos. La tarea que tenemos a cargo es buscar otro de estos métodos de sincronización disponibles en .NET Framework. Esto nos permitirá tener aún más herramientas para atacar diferentes problemas relacionados con el acceso sincrónico a recursos compartidos (e.g., archivos, conexiones de red, conexiones a bases de datos).

2. Solución

En el nombre de espacios System.Threading encontramos la clase Mutex. Esta clase es parte del conjunto básico (primitive) de mecanismos de sincronización para sincronizar (valga el pleonasmo) múltiples procesos.

3. Discusión de la Solución

Ya mencionamos que la clase Mutex [6] comprende otro de los mecanismos de sincronización disponibles en el Framework .NET. Al igual que Monitor [6] y EventWaitHandle [5], Mutex facilita la sincronización de dos o más threads que intenten acceder a un recurso compartido en un instante dado. Sólo uno de estos threads podrá tener acceso exclusivo a la región del recurso compartido para realizar las tareas que comprendan su tratamiento. Básicamente, si un thread que ha tomado el poder sobre el objeto Mutex, el siguiente thread que intente hacerlo será suspendido hasta que el primero libere el objeto Mutex [2].


En conexión con lo anterior, con el uso del método WaitOne sobre un objeto Mutex, un thread se apropia del recurso compartido, y el siguiente thread que intente acceder al recurso compartido permanecerá bloqueado hasta que una de estas acciones ocurra:

  • El thread apropiado del objeto Mutex deberá invocar al método ReleaseMutex [7].
  • El thread apropiado del objeto Mutex deberá invocar al método Close [8].
Por otra parte, es importante agregar que un objeto Mutex forza a la identidad de thread [2], de esta manera se garantiza que el objeto Mutex sólo podrá ser liberado por el thread que se apropio de él. De modo similar:
«The thread that owns a mutex can request the same mutex in repeated calls to WaitOne without blocking its execution. However, the thread must call the ReleaseMutex method the same number of times to release ownership of the mutex.»

Un thread que es finalizado mientras que posee la propiedad de un objeto Mutex, se dice que este último ha sido abandonado. Cuando esto último ocurre se genera la excepción AbandonedMutexException [9]. En [2] encontramos la siguiente advertencia:
Advertencia sobre el un objeto Mutex abandonado
Figura 1. Advertencia sobre el un objeto Mutex abandonado [2].

Versión texto:
«An abandoned mutex often indicates a serious error in the code. When a thread exits without releasing the mutex, the data structures protected by the mutex might not be in a consistent state. The next thread to request ownership of the mutex can handle this exception and proceed, if the integrity of the data structures can be verified.»
Otra nota de advertencia a destacar es la siguiente:
Nota importante sobre la implementación de IDisposable
Figura 2. Nota importante sobre la implementación de IDisposable.

Veamos un par de ejemplos de uso de la clase Mutex y su mecanismo de sincronización:

Ejemplo de uso:

Archivo C# UsoSincronizacionMutex.cs [enlace alternativo]:

En la línea 11 creamos una instancia static de la clase Mutex. Y dos constantes para presentar el número de iteraciones de un thread, y la cantidad de threads a crear, líneas 12 y 13, respectivamente.


A continuación destaco las acciones más importantes que ocurren en el método AccederRecursoCompartido:

  • Línea 50: Apropiación del recurso compartido con la invocación del método WaitOne. El valor pasado como argumento a este método corresponde con la cantidad de tiempo que se debe esperar hasta que se pueda liberar el objeto Mutex.
  • Línea 59: Se simula un tiempo de ejecución de tareas que el recurso compartido requiere para completarse. Los 5 segundos de espera es superior a la suma del tiempo de espera de los otros 2 threads restantes.
  • Línea 66: Liberación del objeto Mutex.
Compilación:

  1. csc /target:exe UsoSincronizacionMutex.cs

Ejecución assembly:

  1. .\UsoSincronizacionMutex.exe


> Prueba de ejecución (local):
Ejecución assembly UsoSincronizacionMutex.exe
Figura 3. Ejecución assembly UsoSincronizacionMutex.exe.
Ejemplo de uso:

A destacar las siguientes líneas:
  • Línea 8: Creación de una instancia static de la clase Mutex.
  • Líneas 10-24:
    • Línea 13: Posesión de la instancia (región crítica) a través de la invocación del método WaitOne.
    • Líneas 17-21: Simulación de escritura de datos en la base de datos.
    • Línea 23: Liberación del objeto Mutex con la invocación del método ReleaseMutex.
Compilación:

  1. csc /target:exe AccesoBaseDatosMutex.cs

Ejecuión assembly:

  1. .\AccesoBaseDatosMutex.exe


> Prueba de ejecución (local):
Ejecución assembly AccesoBaseDatosMutex.exe
Figura 4. Ejecución assembly AccesoBaseDatosMutex.exe.

4. Práctica: Código C#

A través del siguiente ejemplo se demuestra el uso de un objeto Mutex con nombre para acceder a un recurso compartido (en este caso la salida estándar).

Archivo C# RecursoCompartidoConsola.cs [enlace alternativo]:

De este ejemplo debemos destacar las siguientes líneas:
  • Línea 29: Creación de un objeto Mutex asociado con el nombre EjemploMutex. Es posible declararlo dentro de using debido a que Mutex implementa la interfaz IDisposable.
  • Líneas 33-49: Uso de ciclo while para validar si el ciclo se debe ejecutar mientras que la negación lógica del valor de la variable terminarThread resulte verdadero.
  • Línea 36: Al invocar al método WaitOne, el thread actual se apodera del recurso compartido, es decir, el despliegue de mensajes sobre la consola.
  • Línea 40: Se simula una tarea que tarda un segundo en ejecución.
  • Línea 44: Liberación del objeto Mutex.
Por otra parte, dentro del método Main:
  • Líneas 66-68: Creación de tres threads.
  • Líneas 70-72: Inicio de la ejecución de los tres recién creados.
  • Línea 74: Mientras que la tecla Enter no sea presionada, los threads se apoderarán alternativamente del objeto Mutex de forma indefinida.
  • Línea 78: Se rompe el ciclo de las líneas 33-49.
  • Líneas 79-81: Se espera que los threads terminen las últimas tareas encargadas.
Compilación:

  1. csc /target:exe RecursoCompartidoConsola.cs

Ejecución assembly:

  1. .\RecursoCompartidoConsola.exe


> Prueba de ejecución (local):
Ejecución assembly RecursoCompartidoConsola.exe
Figura 5. Ejecución assembly RecursoCompartidoConsola.exe.

5. Artefactos

Enlaces de descarga de los artefactos producidos durante el desarrollo de este artículo:

6. Conclusiones

Hemos realizado un recorrido teórico y práctico sobre los principales elementos que integran el mecanismo de sincronización Mutex. Por medio de la clase Mutex (System.Threading) podemos crear programas basados en múltiples threads que son propensos a acceder a una región crítica de código (recurso compartido); sincronizándolos y evitando la corrupción de datos.

7. Glosario

  • Assembly
  • Ejecución
  • Multithreading
  • Sincronización
  • Thread

8. Literatura & Enlaces

[1]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: Mutex Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.mutex%28v=vs.110%29.aspx
[3]: Receta C# No. 4-8: Sincronizar la Ejecución de Múltiples Threads usando un Evento | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/07/receta-c-no-4-8-sincronizar-la-ejecucion-de-multiples-threads-usando-un-evento.html
[4]: 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
[5]: EventWaitHandle Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.eventwaithandle%28v=vs.110%29.aspx
[6]: Monitor Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.monitor(v=vs.110).aspx
[7]: Mutex.ReleaseMutex Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.mutex.releasemutex%28v=vs.110%29.aspx
[8]: WaitHandle.Close Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.waithandle.close%28v=vs.110%29.aspx
[9]: AbandonedMutexException Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.abandonedmutexexception%28v=vs.110%29.aspx


M

1 comentario:

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.