2009-09-09 13 views
18

Aprecio mucho la atomicidad que proporciona la clase Threading.Interlocked; No entiendo, sin embargo, por qué la función Agregar solo ofrece dos sobrecargas: una para enteros, otra para alarmas. ¿Por qué no Dobles, o cualquier otro tipo numérico para ese asunto?¿Por qué no hay sobrecarga de Interlocked.Add que acepta Dobles como parámetros?

Claramente, el método previsto para cambiar un Doble es CompareExchange; Estoy ADVIERTE que esto se debe a que modificar un Doble es una operación más compleja que modificar un Entero. Aún no me queda claro por qué, si CompareExchange y Add pueden aceptar enteros, tampoco pueden aceptar dobles.

Respuesta

24

La clase Interlocked se ajusta a las funciones Windows API Interlocked **.

Estos son, a su vez, envolviendo alrededor de la API del procesador nativo, utilizando el prefijo de instrucción LOCK para x86. Sólo es compatible con anteponiendo las siguientes instrucciones:

BT, BTS, BTR, BTC, XCHG, xadd, agregar o, ADC, SBB, Y, SUB, XOR, NOT, NEG, INC, DEC

Notarás que estos, a su vez, se corresponden con los métodos de enclavamiento. Desafortunadamente, las funciones ADD para tipos no enteros no son compatibles aquí. Agregar para longitudes de 64 bits es compatible con plataformas de 64 bits.

Aquí hay un gran artículo discussing lock semantics on the instruction level.

+2

El enlace ya no está activo. Aquí hay una copia de seguridad de Internet Archive https://web.archive.org/web/20160319061137/http://www.codemaestro.com/reviews/8 –

-1

Como Adam Robinson señaló, hay una sobrecarga de Interlocked.Increment que toma un Int64, pero nota:

El método de lectura y el de 64 bits sobrecargas del incremento, decremento, y añadido métodos son realmente atómicas solo en sistemas donde System.IntPtr tiene 64 bits de longitud. En otros sistemas, estos métodos son atómicos con respecto a entre sí, pero no con respecto a otros medios de acceso a los datos. Por lo tanto, para que sea seguro para subprocesos en sistemas de 32 bits, cualquier acceso a un valor de 64 bits debe realizarse a través de los miembros de clase Interbloqueada.

1

Sospecho que hay dos razones.

  1. Los procesadores a los que apunta .Net admiten el incremento enclavado solo para tipos enteros. Creo que este es el prefijo LOCK en x86, probablemente existen instrucciones similares para otros procesadores.
  2. Agregar uno a un número de punto flotante puede dar como resultado el mismo número si es lo suficientemente grande, por lo que no estoy seguro de si puede llamar a eso un incremento. Quizás los diseñadores del marco intenten evitar el comportamiento no intuitivo en este caso.
+0

En cuanto a su segundo punto: sí, pero estoy preguntando acerca de Interlocked.Add, no Interlocked.Increment. –

7

Como dijo Reed Copsey, el mapa de operaciones Interbloqueadas (a través de las funciones de la API de Windows) a las instrucciones soportadas directamente por los procesadores x86/x64. Dado que una de esas funciones es XCHG, puedes hacer una operación atómica XCHG sin importar realmente lo que representan los bits en la ubicación objetivo. En otras palabras, el código puede "simular" que el número de punto flotante de 64 bits que está intercambiando es de hecho un entero de 64 bits, y la instrucción XCHG no sabrá la diferencia. Por lo tanto, .Net puede proporcionar Enclavamiento.Funciones de intercambio para flotantes y dobles "pretender" que son enteros y enteros largos, respectivamente.

Sin embargo, todas las otras operaciones realmente hacen funcionar en los bits individuales del destino, por lo que no va a funcionar a menos que los valores realmente representan números enteros (o matrices de bits en algunos casos.)

21

Otros tienen se dirigió al "¿por qué?". Es fácil, sin embargo a rodar su propia Add(ref double, double), utilizando la primitiva CompareExchange:

public static double Add(ref double location1, double value) 
{ 
    double newCurrentValue = location1; // non-volatile read, so may be stale 
    while (true) 
    { 
     double currentValue = newCurrentValue; 
     double newValue = currentValue + value; 
     newCurrentValue = Interlocked.CompareExchange(ref location1, newValue, currentValue); 
     if (newCurrentValue == currentValue) 
      return newValue; 
    } 
} 

CompareExchange establece el valor de location1 ser newValue, si el valor actual es igual currentValue. Como lo hace de una manera atómica y segura para hilos, podemos confiar solo en ella sin recurrir a bloqueos.

¿Por qué el lazo while (true)? Los bucles como este son estándar cuando se implementan algoritmos concurrentemente optimistas. CompareExchange no cambiará location1 si el valor actual es diferente de currentValue. Inicialicé currentValue a location1 - haciendo una lectura no volátil (que puede ser obsoleta, pero eso no cambia la corrección, ya que CompareExchange comprobará el valor). Si el valor actual (fijo) es el que hemos leído en location, CompareExchange cambiará el valor a newValue. Si no, tenemos que volver a intentar CompareExchange con el nuevo valor actual, como se devuelve por CompareExchange.

Si el valor se cambia por otro hilo hasta el momento de nuestro próximo CompareExchange nuevamente, volverá a fallar, necesitando otro reintento, y esto puede continuar en teoría para siempre, de ahí el ciclo. A menos que cambie constantemente el valor de varios hilos, lo más probable es que llame al CompareExchange solo una vez, si el valor actual sigue siendo el que arrojó la lectura no volátil de location1, o dos veces, si fue diferente.

+0

¿Puede explicarnos un poco más cómo funciona esto? ¿Por qué bucle? ¿Y cuál es el papel de CompareExchange? – Mzn

+1

@Mzn Agregado a mi respuesta. Espero que lo explique. –

+2

¡Esta es ahora una excelente respuesta! Al menos ahora creo que sé lo que significa concurrencia optimista: 'Escribir código concurrente con la suposición de que las cosas eventualmente saldrán bien'. En el caso de nuestro tiempo (verdadero) esto significa que no repetiremos para siempre (estamos siendo razonablemente optimistas). ¡Muchas gracias! – Mzn

Cuestiones relacionadas