¿Cómo garantizar que las escrituras de 64 bits sean atómicas?

8 minutos de lectura

¿Cuándo se puede garantizar que las escrituras de 64 bits sean atómicas cuando se programa en C en una plataforma basada en Intel x86 (en particular, una Mac basada en Intel que ejecuta MacOSX 10.4 usando el compilador Intel)? Por ejemplo:

unsigned long long int y;
y = 0xfedcba87654321ULL;
/* ... a bunch of other time-consuming stuff happens... */
y = 0x12345678abcdefULL;

Si otro subproceso está examinando el valor de y después de que la primera asignación a y haya terminado de ejecutarse, me gustaría asegurarme de que vea el valor 0xfedcba87654321 o el valor 0x12345678abcdef, y no una combinación de ellos. Me gustaría hacer esto sin ningún bloqueo y, si es posible, sin ningún código adicional. Mi esperanza es que, al usar un compilador de 64 bits (el compilador Intel de 64 bits), en un sistema operativo capaz de admitir código de 64 bits (MacOSX 10.4), estas escrituras de 64 bits serán atómicas. ¿Es esto siempre cierto?

Su mejor apuesta es evitar intentar construir su propio sistema a partir de primitivas y, en su lugar, usar el bloqueo a menos que De Verdad aparece como un punto caliente al perfilar. (Si cree que puede ser inteligente y evitar los bloqueos, no lo haga. No lo es. Ese es el “usted” general que me incluye a mí y a todos los demás). Como mínimo, debe usar un bloqueo giratorio, vea bloqueo de giro(3). Y hagas lo que hagas, no intente implementar “sus propios” bloqueos. Te equivocarás.

En última instancia, debe usar cualquier operación atómica o de bloqueo que proporcione su sistema operativo. Conseguir este tipo de cosas exactamente correcto en todos los casos es extremadamente difícil. A menudo, puede implicar el conocimiento de cosas como la fe de erratas para versiones específicas de un procesador específico. (“Oh, la versión 2.0 de ese procesador no hizo la indagación de coherencia de caché en el momento correcto, se corrigió en la versión 2.0.1 pero en la 2.0 necesita insertar un NOP.”) Simplemente abofeteando a un volatile palabra clave en una variable en C es casi siempre insuficiente.

En Mac OS X, eso significa que necesita usar las funciones enumeradas en atómico(3) para realizar operaciones verdaderamente atómicas en todas las CPU en cantidades de 32 bits, 64 bits y del tamaño de un puntero. (Utilice este último para cualquier operación atómica en los punteros para que sea compatible con 32/64 bits automáticamente). administración. Afortunadamente el bloqueo de giro(3), atómico(3)y barrera(3) Todas las funciones deberían funcionar correctamente en todas las CPU compatibles con Mac OS X.

  • Gracias por un lugar tan cálido y difuso para enviar futuros evangélicos ‘prácticos’ libres de candados 🙂 + 10 si pudiera.

    – Tim Publicar

    30 de octubre de 2009 a las 2:56

  • En lugar de un spinlock, ¿por qué no usar una barrera de memoria? stackoverflow.com/questions/19965076/…

    – Dai

    11 de junio de 2021 a las 17:34

En x86_64, tanto el compilador Intel como gcc admiten algunas funciones intrínsecas de operación atómica. Aquí está la documentación de gcc de ellos: http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html

Los documentos del compilador de Intel también hablan de ellos aquí: http://softwarecommunity.intel.com/isn/downloads/softwareproducts/pdfs/347603.pdf (página 164 más o menos).

Según el Capítulo 7 de Parte 3A – Guía de programación del sistema de Intel manuales de procesador, los accesos a cuatro palabras se realizarán atómicamente si están alineados en un límite de 64 bits, en un Pentium o más reciente, y no alineados (si aún están dentro de una línea de caché) en un P6 o más nuevo. Deberías usar volatile para asegurarse de que el compilador no intente almacenar en caché la escritura en una variable, y es posible que deba usar una rutina de valla de memoria para asegurarse de que la escritura ocurra en el orden correcto.

Si necesita basar el valor escrito en un valor existente, debe usar las características interbloqueadas de su sistema operativo (por ejemplo, Windows tiene InterlockedIncrement64).

  • Para ser aún más específico, se establece en §8.8.1 en la página 325.

    usuario405725

    27 de octubre de 2013 a las 6:43

  • Si usa la interfaz correcta para atómica correctamente, no necesita volatile.

    –Jeff Hammond

    06/04/2015 a las 22:27

  • §8.1.1 en la página 258: intel.com/content/dam/www/public/us/en/documents/manuals/…

    –Piotr Jurkiewicz

    26 de noviembre de 2018 a las 2:21

  • Utilizando volatile porque la sincronización de subprocesos es incorrecta: no garantiza la atomicidad y no garantiza el orden. Por ejemplo, ++ en un volatile es garantizado ser no atómico (esto es lo que volatile realmente requiere). Enseñando a usar volatile porque la atomicidad es un mal consejo.

    – oxidado

    8 sep 2020 a las 10:30


En Intel MacOSX, puede utilizar las operaciones atómicas del sistema integradas. No se proporciona un conjunto o obtención atómica para enteros de 32 o 64 bits, pero puede crearlo a partir de CompareAndSwap proporcionado. Es posible que desee buscar en la documentación de XCode las diversas funciones de OSAtomic. He escrito la versión de 64 bits a continuación. La versión de 32 bits se puede hacer con funciones de nombre similar.

#include <libkern/OSAtomic.h>
// bool OSAtomicCompareAndSwap64Barrier(int64_t oldValue, int64_t newValue, int64_t *theValue);

void AtomicSet(uint64_t *target, uint64_t new_value)
{
    while (true)
    {
        uint64_t old_value = *target;
        if (OSAtomicCompareAndSwap64Barrier(old_value, new_value, target)) return;
    }
}

uint64_t AtomicGet(uint64_t *target)
{
    while (true)
    {
        int64 value = *target;
        if (OSAtomicCompareAndSwap64Barrier(value, value, target)) return value;
    }
}

Tenga en cuenta que las funciones OSAtomicCompareAndSwap de Apple realizan la operación de forma atómica:

if (*theValue != oldValue) return false;
*theValue = newValue;
return true;

Usamos esto en el ejemplo anterior para crear un método Set tomando primero el valor anterior y luego intentando intercambiar el valor de la memoria de destino. Si el intercambio tiene éxito, eso indica que el valor de la memoria sigue siendo el valor anterior en el momento del intercambio, y se le da el nuevo valor durante el intercambio (que en sí mismo es atómico), por lo que hemos terminado. Si no tiene éxito, entonces algún otro subproceso ha interferido modificando el valor entre cuando lo tomamos y cuando intentamos restablecerlo. Si eso sucede, podemos simplemente hacer un bucle y volver a intentarlo con una penalización mínima.

La idea detrás del método Get es que primero podemos obtener el valor (que puede o no ser el valor real, si otro hilo está interfiriendo). Luego podemos intentar intercambiar el valor consigo mismo, simplemente para verificar que la captura inicial fue igual al valor atómico.

No he verificado esto con mi compilador, así que disculpe cualquier error tipográfico.

Mencionó OSX específicamente, pero en caso de que necesite trabajar en otras plataformas, Windows tiene una serie de funciones Interlocked*, y puede buscarlas en la documentación de MSDN. Algunos de ellos funcionan en Windows 2000 Pro y versiones posteriores, y algunos (particularmente algunas de las funciones de 64 bits) son nuevos con Vista. En otras plataformas, las versiones 4.1 y posteriores de GCC tienen una variedad de funciones __sync*, como __sync_fetch_and_add(). Para otros sistemas, es posible que deba usar ensamblador, y puede encontrar algunas implementaciones en el navegador SVN para el proyecto HaikuOS, dentro de src/system/libroot/os/arch.

En X86, la forma más rápida de escribir atómicamente un valor de 64 bits alineado es usar FISTP. Para valores no alineados, debe usar un CAS2 (_InterlockedExchange64). Sin embargo, la operación CAS2 es bastante lenta debido a BUSLOCK, por lo que a menudo puede ser más rápido verificar la alineación y hacer la versión FISTP para direcciones alineadas. En efecto, así es como el Bloques de construcción Intel Threaded implementa escrituras atómicas de 64 bits.

La última versión de ISO C (C11) define un conjunto de operaciones atómicas, que incluyen atomic_store(_explicit). Ver por ejemplo esta página para más información.

La segunda implementación más portátil de atómicos son los intrínsecos de GCC, que ya se han mencionado. Descubrí que son totalmente compatibles con los compiladores GCC, Clang, Intel e IBM y, desde la última vez que revisé, son parcialmente compatibles con los compiladores Cray.

Una clara ventaja de los átomos C11, además de todo el estándar ISO, es que admiten una prescripción de consistencia de memoria más precisa. Los atómicos de GCC implican una barrera de memoria completa hasta donde yo sé.

avatar de usuario
miguel rebabas

Si desea hacer algo como esto para la comunicación entre subprocesos o entre procesos, entonces necesita tener algo más que una garantía de lectura/escritura atómica. En su ejemplo, parece que desea que los valores escritos indiquen que algún trabajo está en progreso y/o se ha completado. Necesitará hacer varias cosas, no todas portátiles, para asegurarse de que el compilador haya hecho las cosas en el orden que usted quiere (la palabra clave volatile puede ayudar hasta cierto punto) y que la memoria sea consistente. Los procesadores y cachés modernos pueden realizar trabajos fuera de orden sin que el compilador lo sepa, por lo que realmente necesita algún soporte de plataforma (es decir, bloqueos o API entrelazadas específicas de la plataforma) para hacer lo que parece que quiere hacer.

“Cerca de la memoria” o “barrera de la memoria” son términos que quizás desee investigar.

  • mfence es excesivo para una cola productor-consumidor. Solo necesitas sfence del lado del productor y lfence del lado del consumidor. No hay un artículo titulado “Barreras de memoria consideradas dañinas”, pero debería haberlo 🙂

    –Jeff Hammond

    06/04/2015 a las 22:25

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad