¿Cómo las funciones de bloqueo y desbloqueo mutex evitan el reordenamiento de la CPU?

7 minutos de lectura

avatar de usuario
usuario8426277

Hasta donde yo sé, una llamada de función actúa como una barrera del compilador, pero no como una barrera de la CPU.

Esta tutorial dice lo siguiente:

¡adquirir un bloqueo implica adquirir semántica, mientras que liberar un bloqueo implica liberar semántica! Todas las operaciones de memoria intermedias están contenidas dentro de un pequeño y agradable sándwich de barrera, lo que evita cualquier reordenación de memoria no deseada a través de los límites.

Supongo que la cita anterior se refiere al reordenamiento de la CPU y no al reordenamiento del compilador.

Pero no entiendo cómo un bloqueo y desbloqueo mutex hace que la CPU proporcione a estas funciones adquisición y liberación de semántica.

Por ejemplo, si tenemos el siguiente código C:

pthread_mutex_lock(&lock);
i = 10;
j = 20;
pthread_mutex_unlock(&lock);

El código C anterior se traduce en las siguientes (pseudo) instrucciones de ensamblaje:

push the address of lock into the stack
call pthread_mutex_lock()
mov 10 into i
mov 20 into j
push the address of lock into the stack
call pthread_mutex_unlock()

Ahora, ¿qué impide que la CPU se reordene? mov 10 into i y mov 20 into j por arriba call pthread_mutex_lock() o hacia abajo call pthread_mutex_unlock()?

si es el call instrucción que evita que la CPU realice el reordenamiento, entonces, ¿por qué el tutorial que cité hace que parezca que son las funciones de bloqueo y desbloqueo de exclusión mutua las que impiden el reordenamiento de la CPU, por qué el tutorial que cité no decía que cualquier llamada de función evitará el reordenamiento de la CPU?

Mi pregunta es sobre la arquitectura x86.

  • ¿Por qué no recuperó y editó su versión anterior de esto: stackoverflow.com/questions/50948788/…? Esta es la misma pregunta con ediciones menores, y el comentario de Martin aún se aplica.

    – Peter Cordes

    20 de junio de 2018 a las 14:52

  • “Supongo que la cita anterior se refiere al reordenamiento de la CPU y no al reordenamiento del compilador”. Ciertamente, bloquear/desbloquear un pthread mutex DEBE garantizar que el compilador no reordene las instrucciones que está protegiendo. Entonces, la cita habla sobre el reordenamiento del compilador y la CPU: son igualmente importantes desde el punto de vista de un pthread mutex.

    – nos

    20 de junio de 2018 a las 15:23


  • Es el implementaciones de pthread_mutex_lock() y pthread_mutex_unlock() que se dan cuenta de sus promesas sobre el tiempo de ejecución de pedidos. Las CPU que realizan dicho reordenamiento también tienen instrucciones para modularlo, y las funciones de bloqueo/desbloqueo de mutex las usan (entre otras cosas).

    – John Bollinger

    20 de junio de 2018 a las 16:26


  • Y ya voté a favor de su respuesta, @PeterCordes, y me negué a escribir una propia. Pero su respuesta está tan llena de información que, incluso con el resaltado, es fácil pasar por alto este punto, que creo que es el quid de la cuestión.

    – John Bollinger

    20 de junio de 2018 a las 18:23


  • Escribí mi respuesta porque, aunque la respuesta de @PeterCordes es excelente, pensé que esta pregunta también merecía una respuesta “pequeña” simplemente centrándome en lo limitado que pensé que estaba preguntando el OP: ¿cómo evita el compilador/tiempo de ejecución/implementación el reordenamiento de la CPU (no el compilador reordenación). La respuesta es simple: la implementación incluye barreras que impiden el reordenamiento de la CPU. El OP comentó mi respuesta que revela su malentendido original: ese reordenamiento podría ocurrir alrededor call como si fuera cualquier otra instrucción. no puede El reordenamiento ocurre contra el “rastreo dinámico”.

    – BeeOnRope

    20 jun 2018 a las 19:25

  • @user8426277 – porque es el dinámica flujo de instrucciones que (principalmente) es importante para reordenar. La CPU no ejecuta el call como una sola instrucción y luego continúa con la siguiente instrucción en orden fuente/ensamblado, continúa dentro el cuerpo del pthread función. Entonces, para el propósito del análisis, puedes imaginar todo el cuerpo del lock y unlock llama una especie de “en línea” en la asamblea en el lugar donde call aparece la instrucción. ¡Si ese no fuera el caso, los mutexes y muchos otros mecanismos de sincronización serían imposibles de implementar como simples llamadas a funciones!

    – BeeOnRope

    20 de junio de 2018 a las 19:15

  • @ user8426277: actualicé mi pregunta para que quede más clara. La conclusión es que, a nivel de ensamblaje, no se puede hablar sobre qué tipos de reordenamiento son posibles en un call, jmp o cualquier otro cambio de flujo de control porque necesita saber qué sucede en la ubicación a la que saltó.

    – BeeOnRope

    20 de junio de 2018 a las 19:17

  • @PeterCordes Yo nunca digo eso call no tiene efecto liberador verdad? Estoy diciendo que necesitas saber qué hay dentro del call conocer todo el efecto de call en el rastro de instrucción. Por supuesto, el call sí mismo puede tener algunas semánticas de pedido, como las asociadas con la tienda en la pila, pero eso es prácticamente inutilizable y, en mi opinión, es una pista falsa total (y otra respuesta lo cubre bien :-). Tenga en cuenta que liberar-adquirir habla de una ubicación: el lock parte de un mutex no se sincroniza con un push la dirección de retorno en otro hilo, por lo que la liberación no es muy útil.

    – BeeOnRope

    20 de junio de 2018 a las 19:26


  • @PeterCordes: bueno, sí, me pregunto si alguien vendría a decir algo así, y consideré a medias hacerlo más caro, como señalar que el call sí mismo puede prevenir algunos reordenamientos, pero el cuerpo puede entonces prevenir más reordenando Dicho esto, en realidad no creo que tenga sentido decir eso. call tiene “semántica de liberación” (quizás con la implicación de que no necesita una tienda en el lado del unlock implementación): liberar es significativo cuando se habla de un particular ubicación y en el caso de call la ubicación es una ubicación anónima en la pila.

    – BeeOnRope

    20 de junio de 2018 a las 19:33


  • @ user8426277 – correcto, lo entendí. Sin embargo, lo que estoy diciendo es que la CPU no se reordena en la lista de nivel de ensamblaje. Estás imaginando un montaje como call mutex_lock; store 10 to I; y preguntando “¿qué sucede si la CPU reordena la tienda antes de la call? La respuesta es que esa no era la forma correcta de verlo en primer lugar: la CPU no se reordena contra el ensamblaje estático que encuentra en el disco en el binario, opera contra el dinámica secuencia de instrucciones, por lo que debe rastrear mentalmente la ejecución y registrar esa secuencia.

    – BeeOnRope

    22 de junio de 2018 a las 17:34


  • @ArchD.Robison: Un compilador solo podría probar que si las únicas funciones que tocan el static var son ellos mismos staticy no llamado desde ningún otrostatic funciones y no tienen la dirección de la función pasada a ninguna función de caja negra. De lo contrario, debe suponer que la función desconocida puede volver a llamar a esta unidad de traducción para llegar finalmente a una función que lea o escriba la var estática. Las funciones de creación de subprocesos y manejo de señales son una caja negra, por lo que no hay forma de iniciar otro subproceso ejecutando un static función sin tener acceso estático var “escape” de la unidad de traducción.

    – Peter Cordes

    21 de junio de 2018 a las 23:47


  • @PeterCordes Si tiene un conjunto de variables V solo manipuladas por funciones F1 que solo son llamadas por el conjunto de funciones F2 y ninguna de estas puede nombrarse fuera de la TU, y ninguna otra función dentro de la TU toma la dirección para ponerla un objeto accesible en otro lugar… al final del día, V, F1, F2… no sirven para nada.

    – chico curioso

    24 de junio de 2018 a las 3:02

  • @ArchD.Robison Presumiblemente, en un programa realista, las variables estáticas pueden ser indirectamente se accede desde principal.

    – chico curioso

    24 de junio de 2018 a las 3:08

  • @curiousguy: Buen punto. A menos que esta sea la TU que contiene mainentonces tal vez el compilador podría probar que esas variables solo pueden ser utilizadas por el subproceso principal (si se optimiza en función de que sea UB para llamar main de nuevo desde dentro del programa). No creo que haya ningún caso en el que una llamada de función no en línea pueda fallar al ordenar las variables que realmente se comparten.

    – Peter Cordes

    24 de junio de 2018 a las 13:22

  • Estoy de acuerdo con el análisis de @PeterCordes. Había olvidado considerar la posibilidad de “devolver la llamada”.

    – Arco D. Robison

    24/06/2018 a las 23:40

¿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