2010-05-24 8 views
7

Si estoy accediendo a un único tipo de entero (por ejemplo, largo, int, bool, etc.) en múltiples hilos, necesito usar un mecanismo de sincronización como un mutex para bloquearlos. Según tengo entendido, como tipos atómicos, no necesito bloquear el acceso a un único hilo, pero veo que hay muchos códigos que sí usan el bloqueo. Hacer un perfil de este código muestra que hay un impacto significativo en el rendimiento para usar bloqueos, por lo que preferiría no hacerlo. Entonces, si el elemento al que estoy accediendo corresponde a un entero de ancho de bus (por ejemplo, 4 bytes en un procesador de 32 bits) ¿necesito bloquear el acceso a él cuando se usa en múltiples hilos? Dicho de otra manera, si el hilo A está escribiendo en la variable entera X al mismo tiempo que el hilo B lee de la misma variable, es posible que el hilo B pueda terminar unos bytes del valor anterior mezclado con unos pocos bytes de el valor está siendo escrito? ¿Es dependiente de esta arquitectura, p. ¿Está bien para enteros de 4 bytes en sistemas de 32 bits pero inseguros en enteros de 8 bytes en sistemas de 64 bits?¿Debo usar el bloqueo con números enteros en los hilos C++

Edit: Acabo de ver esto related post que ayuda un poco.

Respuesta

7

Nunca está bloqueando un valor: está bloqueando una operación en un valor.

C & C++ no menciona explícitamente los hilos o las operaciones atómicas, por lo que las operaciones que parecen o podrían ser atómicas no están garantizadas por la especificación del lenguaje como atómicas.

Es cierto que sería un compilador bastante desviado el que administraba una lectura no atómica en un int: si tiene una operación que lee un valor, probablemente no sea necesario protegerlo. Sin embargo, podría ser no atómico si abarca un límite de palabras de la máquina.

Operaciones tan simples como m_counter++ implican una operación de búsqueda, incremento y almacenamiento: una condición de carrera: otro hilo puede cambiar el valor después de la recuperación pero antes de la tienda y, por lo tanto, debe protegerse con un mutex, O encuentre su soporte de compiladores para operaciones interconectadas. MSVC tiene funciones como _InterlockedIncrement() que incrementarán de forma segura una ubicación de memoria siempre que todas las otras escrituras estén usando apis interconectados para actualizar la ubicación de la memoria, que es mucho más ligera que invocar una sección crítica.

GCC tiene funciones intrínsecas como __sync_add_and_fetch que también se pueden usar para realizar operaciones interconectadas en valores de palabras de la máquina.

+0

Gracias por esto, InterlockedExchange es probablemente la función que estoy buscando, ya que solo un hilo realmente escribe en la variable en cuestión, mientras que otros simplemente lo leen. –

4

No hay soporte para variables atómicas en C++, por lo que necesita bloqueo. Sin bloqueo, solo se puede especular acerca de qué instrucciones exactas se usarán para la manipulación de datos y si esas instrucciones garantizarán el acceso atómico: esa no es la forma de desarrollar software confiable.

+4

Tengo que estar en desacuerdo (algo). No hay variables atómicas en C++, no, pero tampoco hay bloqueos. Tan pronto como entres en multihilo, debes confiar en las garantías dadas por tu compilador específico. Y * que * tiene muchas garantías para la atomicidad. En general, los accesos a objetos de tamaño de palabra serán atómicos. Por supuesto, el lenguaje C++ no garantiza esto, pero su compilador específico probablemente sí lo haga. Por supuesto, el compilador generalmente hace pocas garantías sobre el reordenamiento, por lo que puede necesitar bloqueos, o al menos barreras de memoria, dependiendo de lo que esté haciendo exactamente. – jalf

3

Sí, sería mejor utilizar la sincronización. Cualquier información a la que accedan varios hilos debe estar sincronizada.

Si es la plataforma de Windows también se puede consultar aquí: Interlocked Variable Access.

2

Sí. Si está en Windows puede echar un vistazo a las funciones/variables Interlocked y si tiene la persuasión Boost entonces puede mirar su implementation of atomic variables.

Si el impulso es demasiado pesado, poner "atomic c++" en su motor de búsqueda favorito le dará mucha comida para pensar.

3

En el 99.99% de los casos, usted debe cerrarse con, incluso si tiene acceso a variables aparentemente atómicas.Como el compilador de C++ no conoce el multi-threading en el nivel del lenguaje, puede hacer muchos reordenamientos no triviales.

Caso en cuestión: Fui mordido por una implementación de bloqueo de giro donde desbloquear es simplemente asignar cero a una variable entera volatile. El compilador estaba reorganizando la operación de desbloqueo antes de la operación real bajo la cerradura, como era de esperar, dando lugar a misteriosos bloqueos.

Ver:

  1. Lock-Free Code: A False Sense of Security
  2. Threads Cannot be Implemented as a Library
+1

No me sorprende en absoluto :) Reordenar es desagradable aquí ... –

+0

Compilador BUG. volátil es un límite de optimización. – Joshua

+2

@Joshua, ¿Siento algo de sarcasmo? Si no, entonces no, no es un error del compilador.Nada impide que el compilador reordene el rojo/escritura no volátil alrededor de los volátiles, solo que no puede reordenar lecturas/escrituras volátiles entre sí. Vea esta pregunta: http://stackoverflow.com/questions/2535148/volatile-qualifier-and-compiler-reorderings –

3

Si estás en una máquina con más de un núcleo, que necesidad de hacer las cosas correctamente, aunque las escrituras de un entero son atómicos. Los problemas son dobles:

  1. ¡Tiene que evitar que el compilador optimice la escritura real! (Algo importante esto; ;-))
  2. Necesita barreras de memoria (no elementos modelados en C) para asegurarse de que los otros núcleos se dan cuenta del hecho de que ha cambiado las cosas. De lo contrario, estarás enredado en cachés entre todos los procesadores y otros detalles sucios como ese.

Si fuera sólo la primera cosa, estaría bien con la variable que marca volatile, pero el segundo es realmente el asesino y sólo realmente va a ver la diferencia en una máquina de múltiples núcleos. Lo que pasa a ser una arquitectura que se está volviendo mucho más común de lo que solía ser ... ¡Vaya! Es hora de dejar de ser descuidado; utilice el código mutex (o sincronización o lo que sea) correcto para su plataforma y todos los detalles de cómo hacer que la memoria funcione como usted cree que desaparecerá.

2

El subprocesamiento múltiple es difícil y complejo. La cantidad de problemas difíciles de diagnosticar que pueden surgir es bastante grande. En particular, en las arquitecturas de Intel, las lecturas y escrituras de enteros de 32 bits alineados se garantiza que son atómicas en el procesador, pero eso no significa que sea seguro hacerlo en entornos de subprocesos múltiples.

Sin las protecciones adecuadas, el compilador y/o el procesador pueden reordenar las instrucciones en su bloque de código. Puede almacenar en caché variables en registros y no estarán visibles en otros subprocesos ...

El bloqueo es costoso, y existen diferentes implementaciones de estructuras de datos sin bloqueo para optimizar el alto rendimiento, pero es difícil hacerlo correctamente. Y el problema es que los errores de concurrencia suelen ser poco claros y difíciles de depurar.

Cuestiones relacionadas