2010-10-04 20 views
25

Lo que (si existe) es el equivalente de C# del comando ASM "XCHG".¿Cómo cambio atómicamente 2 ints en C#?

Con ese comando, que es un intercambio genuino (a diferencia de Interlocked.Exchange), podría simplemente cambiar atómicamente dos ints, que es lo que realmente estoy tratando de hacer.

Actualización:

Código de ejemplo basado en mi sugerencia. Variable con el sufijo "_V" están decoradas tan volátil:

// PART 3 - process links 
// prepare the new Producer 
address.ProducerNew.WorkMask_V = 0; 
// copy the current LinkMask 
address.ProducerNew.LinkMask_V = address.Producer.LinkMask_V; 
// has another (any) thread indicated it dropped its message link from this thread? 
if (this.routerEmptyMask[address.ID] != 0) 
{ 
    // allow all other bits to remain on (i.e. turn off now defunct links) 
    address.ProducerNew.LinkMask_V &= ~this.routerEmptyMask[address.ID]; 
    // reset 
    this.routerEmptyMask[address.ID] = 0; 
} 
// PART 4 - swap 
address.ProducerNew = Interlocked.Exchange<IPC.Producer>(ref address.Producer, address.ProducerNew); 
// PART 5 - lazily include the new links, make a working copy 
workMask = address.Producer.LinkMask_V |= address.ProducerNew.WorkMask_V; 

Nota del perezoso actualización.

+0

Por favor, actualice su pregunta con los detalles adicionales que ha proporcionado en los comentarios a mi respuesta. Ayudará a otros a tratar de responder a su pregunta si conocen todas las restricciones. http://stackoverflow.com/questions/3855671/how-do-i-atomically-swap-2-ints-in-c/3855700#3855700 –

+9

La instrucción XCHG no sería la operación atómica que está buscando ya que no es el código de operación 'XCHG [mem1], [mem2]'. Necesitarías tres instrucciones: 'mov reg, [mem1] XCHG reg, [mem2] mov [mem1], reg' – Skizz

+0

@Skizz He echado otro vistazo al manual de ASM: tienes razón. Guau. Incluso en ensamblador uno no puede hacer lo que estoy buscando. – IamIC

Respuesta

22

He aquí una especie de idea extraña. No sé exactamente cómo configuró su estructura de datos. Pero si es posible que pueda almacenar sus dos valores int en un long, entonces creo que puede cambiarlos atómicamente.

Por ejemplo, digamos que envuelven sus dos valores de la siguiente manera:

class SwappablePair 
{ 
    long m_pair; 

    public SwappablePair(int x, int y) 
    { 
     m_pair = ((long)x << 32) | (uint)y; 
    } 

    /// <summary> 
    /// Reads the values of X and Y atomically. 
    /// </summary> 
    public void GetValues(out int x, out int y) 
    { 
     long current = Interlocked.Read(ref m_pair); 

     x = (int)(current >> 32); 
     y = (int)(current & 0xffffffff); 
    } 

    /// <summary> 
    /// Sets the values of X and Y atomically. 
    /// </summary> 
    public void SetValues(int x, int y) 
    { 
     // If you wanted, you could also take the return value here 
     // and set two out int parameters to indicate what the previous 
     // values were. 
     Interlocked.Exchange(ref m_pair, ((long)x << 32) | (uint)y); 
    } 
} 

entonces parece que podría añadir el Swap método siguiente para dar lugar a un par intercambiado "atómicamente" (en realidad, Don "No sé si es justo decir realmente que la siguiente es atómica; es más como si produce el mismo resultado que un intercambio atómico).

/// <summary> 
/// Swaps the values of X and Y atomically. 
/// </summary> 
public void Swap() 
{ 
    long orig, swapped; 
    do 
    { 
     orig = Interlocked.Read(ref m_pair); 
     swapped = orig << 32 | (uint)(orig >> 32); 
    } while (Interlocked.CompareExchange(ref m_pair, swapped, orig) != orig); 
} 

Es muy posible que haya implementado esto incorrectamente, por supuesto. Y podría haber un error en esta idea. Es solo una idea

+3

+1 para pensar fuera de la caja. –

+0

Maldita sea, ¿por qué no pensé en almacenar las 2 ints en mucho tiempo? ¡Bien! – IamIC

+2

La técnica se conoce a partir de una solución al problema ABA donde se mantiene un contador de 'versión' incremental en los 32 bits superiores y sus datos en los 32 inferiores, para detectar fallas ABA. Consulte la página 536 de la "Programación simultánea en Windows" de Duffy. Escribí un diccionario administrado sin bloqueos basado en operaciones atómicas de 64 bits, hecho completamente en vecinos emparejados de 32 bits como este. –

23

¿Por qué no es Interlocked.Exchange adecuado para usted?

Si necesita cambiar las ubicaciones exactas de la memoria, está utilizando un lenguaje y una plataforma incorrectos, ya que .NET abstrae la gestión de la memoria para que no tenga que pensar en ello.

Si tiene que hacer algo como esto sin Interlocked.Exchange, podría escribir algo de código marcado como unsafe y hacer un intercambio tradicionales basados ​​en punteros como puede ser que en C o C++, pero que había necesidad de envolverlo en una sincronización adecuada contexto para que sea una operación atómica.

actualización
No es necesario recurrir a unsafe código para hacer un intercambio atómicamente. Puede ajustar el código en un contexto de sincronización para que sea atómico.

lock (myLockObject) 
{ 
    var x = Interlocked.Exchange(a, b); 
    Interlocked.Exchange(b, x); 
} 

Actualización 2
Si la sincronización no es una opción (como se indica en los comentarios), entonces yo creo que estás fuera de suerte. A medida que persigue una eficiencia no medida, es posible que desee concentrarse en otro lugar. Si el intercambio de dos valores enteros es un gran obstáculo de rendimiento, probablemente esté utilizando la plataforma incorrecta.

+0

No es que trate de entrar en la administración de la memoria. Simplemente necesito cambiar los valores de dos puntos y hacerlo de forma atómica. Debido a mis requisitos de diseño, un bloqueo no es una opción. – IamIC

+1

@IanC: ¿Por qué un bloqueo no es apropiado? Quizás si eres más comunicativo con TODOS los detalles, puedes obtener una respuesta. Goteo de restricciones adicionales a medida que avanzamos no es útil. –

+0

@Jeff Estoy tratando de mantenerlo simple ya que el intercambio es todo lo que me interesa :). La aplicación requiere 1 hilo para escribir millones de mensajes por segundo y otro hilo lo lee de forma relativamente ocasional (1000/seg). Una cerradura sería demasiada sobrecarga innecesaria, ya que tendría que bloquear/desbloquear sin contención millones de veces por segundo. Mi código actual funciona bien, pero si pudiera cambiar las 2 entradas, sería más eficiente. – IamIC

8

Interlocked.Exchange es realmente la única cosa que puede hacer:

var x = Interlocked.Exchange(a, b); 
    Interlocked.Exchange(b, x); 

Tiene razón en que esto no será atómica, pero con una variable local utiliza se le garantiza que los valores son consistentes, siempre y cuando ambas líneas ejecutar. Sus otras opciones son un código inseguro (para usar punteros), usar p/invoke a una biblioteca nativa o rediseñar para que ya no sea necesario.

+0

Tiene que ser atómico. ¿Podría indicarme una referencia sobre cómo hacer esto con un código inseguro? – IamIC

+0

Interlocked.Exchange es una operación atómica, sin embargo, 2 en una fila no están sin sincronización. Como se indica en la documentación de MSDN, http://msdn.microsoft.com/en-us/library/system.threading.interlocked.exchange.aspx –

+0

@IanC inseguro C# no lo convertirá en * más * atómico que eso. Si realmente, realmente necesitas XCHG, tendrás que escribirlo tú mismo en c. Sin embargo, no estoy seguro de que la transición le proporcione una ventaja de rendimiento enorme sobre un bloqueo delgado. Ver algo como esto: http://www.codeproject.com/KB/cs/unmanage.aspx –

0

Fuera de Interlocked.Exchange, asumo que el comando XCHG es probablemente una implementación del XOR Swap, por lo que podría escribir el suyo.

C sintaxis: (de enlace de Wikipedia)

void xorSwap (int *x, int *y) { 
    if (x != y) { 
     *x ^= *y; 
     *y ^= *x; 
     *x ^= *y; 
    } 
} 

Editar esto no es atómico, que tendría que sincronizar usted mismo

+6

puro. Eso no sería atómico según los requisitos del OP. . –

+1

XCHG es una instrucción de CPU x86 y es atómica en el sentido de que no puede ser interrumpida por otra instrucción. El intercambio XOR no es atómico sin agregar un bloqueo adicional en los parámetros primero. – Rup

+0

+1 para el intercambio XOR, aunque no es una rutina atómica. –

5

Según MSDN, Interlocked.Exchange es atómica.

Si no es adecuado para usted, puede implementar XCHG en una sección insegura usando C/C++.

+0

Esa parece ser la única solución. Gracias. – IamIC

+0

Sin bloqueos, no veo cómo funcionará el código inseguro. No se puede convertir en atómico sin algún tipo de sincronización. –

+0

@Jeff Yates Tienes razón en la parte atómica.Lo que quería decir es que, en la sección insegura, podrías usar el ensamblador en línea para hacer el XCHG. –

58

Ésta es la aplicación probable para Interlocked.Exchange() en el CLR, copiada de la fuente SSCLI20:

FASTCALL_FUNC ExchangeUP,8 
     _ASSERT_ALIGNED_4_X86 ecx 
     mov  eax, [ecx]  ; attempted comparand 
retry: 
     cmpxchg [ecx], edx 
     jne  retry1   ; predicted NOT taken 
     retn 
retry1: 
     jmp  retry 
FASTCALL_ENDFUNC ExchangeUP 

Es superior a la utilización de XCHG ya que este código funciona sin tener una cerradura de autobuses. El código de salto de aspecto extraño es una optimización en caso de que los datos de predicción de bifurcación no estén disponibles. Huelga decir que tal vez, tratando de hacer un mejor trabajo que lo que ha sido reflexionado durante muchos años por muy buenos ingenieros de software con generosas aportaciones de los fabricantes de chips, es una gran tarea.

+10

De hecho. Indubitablemente Bastante. +1 –

+0

¿Por qué sería útil el salto si los datos de predicción de ramas no están disponibles? – TheBlastOne

+1

@the - Si no está disponible, entonces el procesador tiene que adivinar. Siempre adivina "no saltar". Lo cual es muy probable que sea correcto con este código. Muy probablemente mal si se usaron las instrucciones je. Adivinar mal es muy caro debido a la tubería. –

1

Interlocked.Exchange es la mejor manera de intercambiar dos valores int de manera segura para subprocesos en C#.

Utilice esta clase, incluso si tiene un ordenador multiprocesador y nunca sabe en qué procesador se ejecutará su hilo.

0

Creo que acabo de encontrar la mejor solución. Es:

Interlocked.Exchange() Método (ref T, T)

Todas las "nuevas" variables se pueden definir en una clase (Of T) y la intercambian con las variables actuales. Esto permite que se produzca una instantánea atómica, al tiempo que se intercambia efectivamente cualquier cantidad de variables.

+0

Sí, el único problema que veo con este enfoque es que, de repente, debe colocar sus datos en un tipo de referencia inmutable, lo que puede ser una exageración, dependiendo de la frecuencia con la que realiza intercambios. –

+0

@Dan, no veo ningún problema con eso. Esto está cambiando el puntero solo, obviamente. No afectará la escritura a la velocidad del objeto. – IamIC

+0

@IanC: Solo quiero decir que, si te entiendo correctamente, necesitarías crear un nuevo objeto para cada intercambio (como Interbloqueado.Exchange (par, par nuevo (par.X, par.Y))). Tal vez está bien; solo parecía un poco ... pesado? O tal vez he entendido mal. –