c# - ¿Interlocked.CompareExchange usa una barrera de memoria?
multithreading optimization (6)
Cualquier instrucción x86 que tenga un prefijo de bloqueo tiene una barrera de memoria completa . Como se muestra en la respuesta de Abel, las API de Interlocked * y CompareExchanges utilizan instrucciones de bloqueo prefijadas, como el lock cmpxchg
. Por lo tanto, implica memoria valla.
Sí, Interlocked.CompareExchange utiliza una barrera de memoria.
¿Por qué? Porque los procesadores x86 lo hicieron. Del Volumen 3A de Intel : Guía de programación del sistema, parte 1 , sección 7.1.2.2:
Para los procesadores de la familia P6, las operaciones bloqueadas serializan todas las operaciones de carga y almacenamiento pendientes (es decir, esperan a que se completen). Esta regla también es válida para los procesadores Pentium 4 e Intel Xeon, con una excepción. Las operaciones de carga que hacen referencia a tipos de memoria mal ordenados (como el tipo de memoria de WC) pueden no ser serializadas.
volatile
no tiene nada que ver con esta discusión. Esto es sobre operaciones atómicas; para admitir operaciones atómicas en la CPU, x86 garantiza que todas las cargas y almacenes anteriores se completarán.
Estoy leyendo la publicación de Joe Duffy sobre lecturas y escrituras volátiles, y puntualidad , y estoy tratando de entender algo sobre el último ejemplo de código de la publicación:
while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ;
m_state = 0;
while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ;
m_state = 0;
…
Cuando se ejecuta la segunda operación CMPXCHG, ¿utiliza una barrera de memoria para garantizar que el valor de m_state sea realmente el último valor que se le haya escrito? ¿O solo utilizará algún valor que ya esté almacenado en la memoria caché del procesador? (Suponiendo que m_state no se declare como volátil).
Si comprendo correctamente, si CMPXCHG no usará una barrera de memoria, entonces el procedimiento de adquisición del bloqueo completo no será justo, ya que es muy probable que el hilo que fue el primero en adquirir el bloqueo será el que adquiera todos de las siguientes cerraduras . ¿Comprendí correctamente o me estoy perdiendo algo aquí?
Edición : la pregunta principal es en realidad si llamar a CompareExchange causará una barrera de memoria antes de intentar leer el valor de m_state. Entonces, si la asignación de 0 será visible para todos los subprocesos cuando intenten llamar a CompareExchange nuevamente.
Las funciones interbloqueadas están garantizadas para detener el bus y la CPU mientras resuelve los operandos. La consecuencia inmediata es que ningún cambio de hilo, en su CPU u otro, interrumpirá la función de enclavamiento en medio de su ejecución.
Dado que está pasando una referencia a la función c #, el código del ensamblador subyacente funcionará con la dirección del entero real, por lo que el acceso a la variable no se optimizará. Funcionará exactamente como se esperaba.
edición: aquí hay un enlace que explica mejor el comportamiento de la instrucción asm: http://faydoc.tripod.com/cpu/cmpxchg.htm
Como puede ver, el bus se detiene al forzar un ciclo de escritura, por lo que cualquier otro "subproceso" (leer: otros cpu cores) que intentaría usar el bus al mismo tiempo se pondría en una cola de espera.
Parece que hay algunas comparaciones con las funciones de la API de Win32 por el mismo nombre, pero este hilo es todo acerca de la clase de Interlocked
C #. Desde su propia descripción, se garantiza que sus operaciones son atómicas. No estoy seguro de cómo eso se traduce en "barreras de memoria completa" como se menciona en otras respuestas aquí, pero juzgue por sí mismo.
En los sistemas con un solo procesador, no sucede nada especial, solo hay una instrucción:
FASTCALL_FUNC CompareExchangeUP,12
_ASSERT_ALIGNED_4_X86 ecx
mov eax, [esp+4] ; Comparand
cmpxchg [ecx], edx
retn 4 ; result in EAX
FASTCALL_ENDFUNC CompareExchangeUP
Pero en los sistemas multiprocesador, se utiliza un bloqueo de hardware para evitar que otros núcleos accedan a los datos al mismo tiempo:
FASTCALL_FUNC CompareExchangeMP,12
_ASSERT_ALIGNED_4_X86 ecx
mov eax, [esp+4] ; Comparand
lock cmpxchg [ecx], edx
retn 4 ; result in EAX
FASTCALL_ENDFUNC CompareExchangeMP
Una lectura interesante aquí y allá con algunas conclusiones erróneas, pero en general excelente sobre el tema es esta moserware.com/2008/09/how-do-locks-lock.html .
Según ECMA-335 (sección I.12.6.5):
5. Explotaciones atómicas explícitas. La biblioteca de clases proporciona una variedad de operaciones atómicas en la clase System.Threading.Interlocked. Estas operaciones (p. Ej., Incremento, Disminución, Intercambio y CompareExchange) realizan operaciones implícitas de adquisición / liberación .
Por lo tanto, estas operaciones siguen el principio de menos asombro .
MSDN dice acerca de las funciones de la API de Win32: " La mayoría de las funciones entrelazadas proporcionan barreras de memoria completas en todas las plataformas de Windows "
(Las excepciones son funciones entrelazadas con semántica explícita de adquisición / lanzamiento)
A partir de eso, llego a la conclusión de que el Interlocked de C # runtime ofrece las mismas garantías, ya que están documentadas con un comportamiento idéntico (y se resuelven en sentencias de CPU intrínsecas en las plataformas que conozco). Desafortunadamente, debido a la tendencia de MSDN a presentar muestras en lugar de documentación, no se explica explícitamente.
ref
no respeta las reglas volatile
habituales, especialmente en cosas como:
volatile bool myField;
...
RunMethod(ref myField);
...
void RunMethod(ref bool isDone) {
while(!isDone) {} // silly example
}
En este caso, no se garantiza que isDone
cambios externos en isDone
aunque el campo subyacente ( myField
) sea volatile
; RunMethod
no lo sabe, por lo que no tiene el código correcto.
¡Sin embargo! Esto debería ser un no-problema:
- Si usa
Interlocked
, entonces useInterlocked
para todos los accesos al campo - Si está utilizando el
lock
, utilice ellock
para todos los accesos al campo
Siga esas reglas y debería funcionar bien.
Re la edición; Sí, ese comportamiento es una parte crítica de Interlocked
. Para ser honesto, no sé cómo se implementa (barrera de memoria, etc. - tenga en cuenta que son métodos de "Llamada interna", por lo que no puedo verificar ;-p) - pero sí: las actualizaciones de un hilo serán inmediatamente visibles para todos los demás , siempre y cuando utilicen los métodos de Interlocked
(de ahí mi punto anterior).