jueves, 7 de agosto de 2014

Receta C# No. 4-11: Sincronizar el Acceso a Valores de Datos Compartidos

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Clase static Interlocked
3.2 Método Interlocked.Add
3.3 Métodos Interlocked.Increment e Interlocked.Decrement
3.4 Método Interlocked.Exchange
3.5 Rendimiento: locked vs Interlocked
3.6 Resumen
4. Práctica: Código C#
5. Artefactos
6. Conclusiones
7. Glosario
8. Literatura & Enlaces

0. Introducción

En la receta multithreading 1-9, Sincronización con la Palabra Clave lock, comprendimos cómo usar la sentencia de bloqueo concurrente lock, a un recurso compartido con el uso de un objeto bloqueante. Conoceremos una alternativa más simple y directa para operaciones aritméticas atómicas sobre valores numéricos. Para esto tendremos que usar los elementos de programa de la clase Interlocked. Realizaremos ejemplos de uso para demostrar su simplicidad y su alto desempeño frente a lock.

1. Problema

Hallar un método de sincronización del acceso a valores de datos compartidos. Las operaciones se deben realizar de forma atómica de esta manera removiendo cualquier posibilidad de errores o corrupción de los datos por parte de los threads.

2. Solución

Siempre resulta que Microsoft .NET Framework cuenta con una infraestructura de artefactos software para cubrir las necesidades de construcción de soluciones de software. En esta oportunidad no es la excepción; y hemos consultado la MSDN para encontrarnos con la clase Interlocked, la cual es capaz de realizar operaciones atómicas sobre valores de datos. Esta clase aisla cualquier intento de acceso concurrente a una variable, de forma más simple y directa que con el uso de la sentencia lock, evitando la corrupción de los datos compartidos.

3. Discusión de la Solución

3.1 Clase static Interlocked

La clase Interlocked [2] es una clase estática definida en el nombre de espacios System.Threading. Su tarea es proveer operaciones atómicas sobre valores de datos compartidos asegurando su consistencia a través de la seguridad en el acceso concurrente a datos compartidos por múltiples threads. Cuenta con los siguientes miembros método:
  • Add: Método sobrecargado para operación atómica de suma. (2 versiones sobrecargadas.)
  • CompareExchange: Compara dos valores de datos, y alterna su valor en caso de ser iguales. (Hasta 7 versiones sobrecargadas.)
  • Decrement: Decrementa atómicamente en una unidad el valor de un dato. (2 versiones sobrecargadas.)
  • Exchange: Alterna (asignación) el valor de un valor de dato. (7 versiones sobrecargadas).
  • Increment: Incrementa en una unidad el valor de un dato. (2 versiones sobrecargadas).
  • MemoryBarrier: Sincroniza el acceso a memoria.
  • Read: Lee el valor almacenado en valor de dato.
A continuación describiremos 3 de estos métodos.

3.2 Método Interlocked.Add

El método Interlocked.Add [3] suma un determinado valor a una variable pasada como argumento. Esta es su firma para números enteros de 32 bits [4]:

public static int Add( ref int location1, int value)

Descripción puntual:
  • Parámetros:
    • location1: Variable pasada como referencia a la que se le sumará el valor del segundo parámetro.
    • value: Valor a sumar a la primera variable.
  • Valor de retorno: Entero de 32 bits. Resultado de la suma almacenado sobre la instancia entera location1.
Ejemplo de uso:

En la línea 9 declaramos una variable static que usaremos para sincronizar entre distintos threads proclives a usar su valor y alterarlo. Más adelante, en las líneas 14 y 15 creamos dos instancias de la clase Thread, las iniciamos, y esperamos su finalización (líneas 22 y 23). En el método Sumar (líneas 29-33) usamos el método Add de Interlocked para sumar el valor 2 a la variable variable. Finalmente, en la línea 26 mostramos en la salida estándar el valor final de variable.

Compilación:


  1. csc /target:exe UsoAdd.cs

Ejecución assembly:


  1. .\UsoAdd.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly UsoAdd.exe
Figura 1. Ejecución assembly UsoAdd.exe.

3.3 Métodos Interlocked.Increment y Interlocked.Decrement

Los métodos Increment [5] y Decrement [6] cuentan con versiones sobrecargadas para los tipos numéricos Int32 y Int64. Increment Incrementa en una unidad el valor pasado como argumento y asigna el resultado al mismo argumento pasado como referencia. De manera similar, el método Decrement decrementa en una unidad el valor pasado como argumento, el resultado es asignado a la variable pasada como referencia.

Ejemplo de uso:

Archivo C# UsoIncrementDecrement.cs [enlace alternativo]:

En el método IncrementarDecrementar (líneas 29-35) usamos los métodos Increment (línea 32) y Drecrement (líneas 33 y 34) para incrementar y decrementar en una unidad el valor de la variable private static variable.

Compilación:


  1. csc /target:exe UsoIncrementDecrement.cs

Ejecución assembly:


  1. .\UsoIncrementDecrement.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly UsoIncrementDecrement.exe
Figura 2. Ejecución assembly UsoIncrementDecrement.exe.

3.4 Método Interlocked.Exchange

A través del método Exchange [7] asignamos un valor a la variable pasada como primer argumento. Usaremos como ejemplo la firma [8]:

public static int Exchange( ref int location1, int value)

Descripción puntual:
  • Parámetros:
    • location1: Variable a asignar el valor del segundo parámetro.
    • value1: Valor a asignar.
  • Valor de retorno: Entero de 32 bits asignado a la variable pasada como referencia en el primer parámetro.
Ejemplo de uso:

En el método Incrementar (líneas 26-36) usamos el método Interlocked.Excange (línea 29) para asignar el valor entero 10 a variable. Más adelante, sobre la línea 32, comparamos si el valor almacenado en variable es igual al segundo argumento (20), de ser así, se asignaría 10 a variable.

Compilación:


  1. csc /target:exe UsoExchange.cs

Ejecución assembly:


  1. .\UsoExchange.exe

> Prueba de ejecución (ideone.com).

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

3.5 Rendimiento: lock vs Interlocked

Realicemos una prueba de rendimiento para evidenciar las diferencias de tiempo de respuesta de uso de la construcción lock y los métodos estáticos de la clase Interlocked.

Las instancias de Stopwatch de las líneas 19 y 32 cronometrizan el tiempo que toma la ejecución de uso de diez millones de bloqueos sincrónicos con lock y con la invocación del método Interlocked.Increment, respectivamente.

Compilación:


  1. csc /target:exe RendimientoInterlocked..cs

Ejecución assembly:


  1. .\RendimientoInterlocked.exe

> Prueba de ejecución (ideone.com):

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

Medidas estadísticas básicas:
  • Datos:
    • locked: {34.96, 35.28, 35.68, 36.51, 35.15} ns
    • Interlocked.Increment: {15.82, 15.46, 15.45, 16.06, 15.37}
  • Mínimo:
    • locked: 34.96 ns
    • Interlocked.Increment: 15.45 ns
  • Máximo: 
    • locked: 36.51 ns
    • Interlocked.Increment: 16.06 ns
  • Media:
    • locked: 35.52 ns
    • Interlocked.Increment: 15.63 ns

3.6 Resumen

En la Figura 5 [1] se resaltan los métodos de sincronización de valores de datos disponibles en la clase Interlocked.
Resumen de métodos de Interlocked
Figura 5. Resumen de métodos de Interlocked.

En [1] también advierten sobre el uso de operaciones atómicas sobre plataformas de sistema de 32 y 64 bits.
Advertencia sobre el uso de operaciones atómicas sobre sistemas de 32 y 64 bits
Figura 6. Advertencia sobre el uso de operaciones atómicas sobre sistemas de 32 y 64 bits.
Versión texto:
«The atomicity of 64-bit interlocked operations on 32-bit platforms are guaranteed only when the data value is accessed through the Interlocked class -i.e., that the variable is not accessed directly.»

4. Práctica: Código C#

En los ejemplos de uso de la sección 3 se demostró el uso de varios threads en el intento de modificar el valor de una variable compartida. En este ejemplo (adaptado de [1]) solo se mostrará el uso sintáctico de los métodos estáticos de Interlocked para demostrar sus efectos.

Archivo C# UsoClaseInterlocked.cs [enlace alternativo]:


Compilación:


  1. csc /target:exe UsoClaseInterlocked.cs

Ejecución assembly:


  1. .\UsoClaseInterlocked.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly UsoClaseInterlocked.exe
Figura 7. Ejecución assembly UsoClaseInterlocked.exe.

5. Artefactos

Enlaces de descarga de los artefactos producidos en el desarrollo de esta receta C#:

6. Conclusiones

El uso de los métodos disponibles en la clase Interlocked facilita la sincronización de acceso a recursos compartidos, en este caso estudiado y puesto en práctica, de valores de datos. También se demostró la diferencia en desempeño (tiempo de respuesta) de la construcción locked y los métodos de la clase Interlocked: esta última presenta mejor tiempo respuesta (Mínimo: 34.96 ns [locked] vs 15.45 ns [Interlocked]). Estudiaremos la próxima receta para comprender cuando un thread ha finalizado.

7. Glosario

  • Hilo
  • Interlocked
  • MSDN
  • Multithreading
  • Recurso compartido
  • 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]: Interlocked Class (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.interlocked%28v=vs.110%29.aspx
[3]: Interlocked.Add Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.interlocked.add(v=vs.110).aspx
[4]: Interlocked.Add Method (Int32, Int32) (System.Threading) - http://msdn.microsoft.com/en-us/library/33821kfh(v=vs.110).aspx
[5]: Interlocked.Increment Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.interlocked.increment(v=vs.110).aspx
[6]: Interlocked.Increment Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.interlocked.increment(v=vs.110).aspx
[7]: Interlocked.Exchange Method (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.interlocked.exchange(v=vs.110).aspx
[8]: Receta Multithreading en C# No. 1-9: Sincronización con la Palabra Clave lock | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/07/receta-multithreading-en-csharp-no-1-9-sincronizacion-con-la-palabra-clave-lock.html


M

No hay comentarios:

Publicar un comentario

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