Soy nuevo en multihilo y estaba tratando de entender cómo funcionan los mutexes. Busqué mucho en Google, pero aún dejó algunas dudas sobre cómo funciona porque creé mi propio programa en el que el bloqueo no funcionaba.
Una sintaxis absolutamente no intuitiva del mutex es pthread_mutex_lock( &mutex1 );
, donde parece que el mutex está bloqueado, cuando lo que realmente quiero bloquear es alguna otra variable. ¿Esta sintaxis significa que bloquear un mutex bloquea una región de código hasta que el mutex se desbloquea? Entonces, ¿cómo saben los hilos que la región está bloqueada? [ ACTUALIZACIÓN: los subprocesos saben que la región está bloqueada, por cercado de memoria ]. ¿Y no se supone que ese fenómeno se llama sección crítica? [ ACTUALIZACIÓN: Los objetos de sección crítica están disponibles solo en Windows, donde los objetos son más rápidos que los mutexes y son visibles solo para el hilo que lo implementa. De lo contrario, la sección crítica solo se refiere al área de código protegida por un mutex ]
En resumen, ¿podría ayudarnos con el programa de ejemplo mutex más simple posible y la explicación más simple posible sobre la lógica de cómo funciona? Estoy seguro de que esto ayudará a muchos otros novatos.
Respuestas:
Aquí va mi humilde intento de explicar el concepto a los novatos de todo el mundo: (una versión codificada por colores en mi blog también)
Muchas personas corren a una cabina telefónica solitaria (no tienen teléfonos móviles) para hablar con sus seres queridos. La primera persona que agarra la manija de la puerta de la cabina es la que tiene permitido usar el teléfono. Tiene que seguir agarrando la manija de la puerta mientras usa el teléfono, de lo contrario, alguien más agarrará la manija, lo arrojará y hablará con su esposa :) No hay un sistema de colas como tal. Cuando la persona termina su llamada, sale de la cabina y deja la manija de la puerta, la siguiente persona que la agarre podrá usar el teléfono.
Un hilo es: Cada persona
El mutex es: El tirador de la puerta
La cerradura es: La mano de la persona
El recurso es: El teléfono
Cualquier subproceso que tenga que ejecutar algunas líneas de código que no deben ser modificadas por otros subprocesos al mismo tiempo (usando el teléfono para hablar con su esposa), primero debe adquirir un candado en un mutex (agarrando la manija de la puerta de la cabina ) Solo entonces un hilo podrá ejecutar esas líneas de código (haciendo la llamada telefónica).
Una vez que el hilo ha ejecutado ese código, debe liberar el bloqueo en el mutex para que otro hilo pueda adquirir un bloqueo en el mutex (otras personas pueden acceder a la cabina del teléfono).
[ El concepto de tener un mutex es un poco absurdo cuando se considera el acceso exclusivo del mundo real, pero en el mundo de la programación, supongo que no había otra manera de permitir que los otros hilos 'vean' que un hilo ya estaba ejecutando algunas líneas de código. Hay conceptos de mutex recursivos, etc., pero este ejemplo solo pretendía mostrarle el concepto básico. Espero que el ejemplo te dé una idea clara del concepto. ]
Con roscado C ++ 11:
Compila y ejecuta usando
g++ -std=c++0x -pthread -o thread thread.cpp;./thread
En lugar de usar explícitamente
lock
yunlock
, puede usar corchetes como se muestra aquí , si está usando un bloqueo de alcance para la ventaja que proporciona . Sin embargo, las cerraduras con alcance tienen un ligero rendimiento general.fuente
(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer
. En cuanto al uso del bloqueo de ámbito, depende del desarrollador, dependiendo del tipo de aplicación que estén creando. Esta respuesta tenía como objetivo abordar la comprensión básica del concepto mutex y no entrar en todas las complejidades del mismo, por lo que sus comentarios y enlaces son bienvenidos, pero están un poco fuera del alcance de este tutorial.Si bien un mutex puede usarse para resolver otros problemas, la razón principal por la que existen es para proporcionar una exclusión mutua y, por lo tanto, resolver lo que se conoce como una condición de carrera. Cuando dos (o más) subprocesos o procesos intentan acceder a la misma variable al mismo tiempo, tenemos potencial para una condición de carrera. Considere el siguiente código
Los aspectos internos de esta función se ven muy simples. Es solo una declaración. Sin embargo, un equivalente típico del lenguaje de seudoensamblaje podría ser:
Debido a que todas las instrucciones equivalentes en lenguaje ensamblador son necesarias para realizar la operación de incremento en i, decimos que incrementar i es una operación no atómica. Una operación atómica es aquella que puede completarse en el hardware con la garantía de no ser interrumpida una vez que la ejecución de la instrucción ha comenzado. El incremento i consiste en una cadena de 3 instrucciones atómicas. En un sistema concurrente donde varios hilos llaman a la función, surgen problemas cuando un hilo lee o escribe en el momento equivocado. Imagina que tenemos dos subprocesos que se ejecutan simultáneamente y uno llama a la función inmediatamente después del otro. Digamos también que hemos inicializado a 0. También suponga que tenemos muchos registros y que los dos hilos están usando registros completamente diferentes, por lo que no habrá colisiones. El momento real de estos eventos puede ser:
Lo que sucedió es que tenemos dos subprocesos que se incrementan simultáneamente, nuestra función se llama dos veces, pero el resultado es inconsistente con ese hecho. Parece que la función solo se llamó una vez. Esto se debe a que la atomicidad está "rota" en el nivel de la máquina, lo que significa que los hilos pueden interrumpirse entre sí o trabajar juntos en los momentos incorrectos.
Necesitamos un mecanismo para resolver esto. Necesitamos imponer algunos pedidos a las instrucciones anteriores. Un mecanismo común es bloquear todos los hilos excepto uno. Pthread mutex utiliza este mecanismo.
Cualquier subproceso que tenga que ejecutar algunas líneas de código que puedan modificar de forma insegura los valores compartidos por otros subprocesos al mismo tiempo (usando el teléfono para hablar con su esposa), primero debe hacerse bloquear un mutex. De esta manera, cualquier subproceso que requiera acceso a los datos compartidos debe pasar a través del bloqueo mutex. Solo entonces un hilo podrá ejecutar el código. Esta sección de código se llama una sección crítica.
Una vez que el hilo ha ejecutado la sección crítica, debe liberar el bloqueo en el mutex para que otro hilo pueda adquirir un bloqueo en el mutex.
El concepto de tener un mutex parece un poco extraño cuando se considera que los humanos buscan acceso exclusivo a objetos físicos reales, pero al programar, debemos ser intencionales. Los procesos y subprocesos concurrentes no tienen la educación social y cultural que nosotros tenemos, por lo que debemos obligarlos a compartir datos de manera agradable.
Técnicamente hablando, ¿cómo funciona un mutex? ¿No sufre las mismas condiciones de carrera que mencionamos anteriormente? ¿No es pthread_mutex_lock () un poco más complejo que un simple incremento de una variable?
Técnicamente hablando, necesitamos un poco de soporte de hardware para ayudarnos. Los diseñadores de hardware nos dan instrucciones de la máquina que hacen más de una cosa pero están garantizadas para ser atómicas. Un ejemplo clásico de tal instrucción es el test-and-set (TAS). Al tratar de adquirir un bloqueo en un recurso, podríamos usar el TAS para verificar si un valor en la memoria es 0. Si es así, esa sería nuestra señal de que el recurso está en uso y no hacemos nada (o más exactamente , esperamos por algún mecanismo. Un mutex pthreads nos pondrá en una cola especial en el sistema operativo y nos notificará cuando el recurso esté disponible. Los sistemas Dumber pueden requerir que hagamos un ciclo de giro cerrado, probando la condición una y otra vez) . Si el valor en la memoria no es 0, el TAS establece la ubicación en algo distinto de 0 sin utilizar ninguna otra instrucción. Eso' s como combinar dos instrucciones de ensamblaje en 1 para darnos atomicidad. Por lo tanto, probar y cambiar el valor (si el cambio es apropiado) no se puede interrumpir una vez que ha comenzado. Podemos construir mutexes sobre tal instrucción.
Nota: algunas secciones pueden parecer similares a una respuesta anterior. Acepté su invitación a editar, él prefería la forma original en que estaba, así que me quedo con lo que tenía, que está infundido con un poco de su palabrería.
fuente
El mejor tutorial de hilos que conozco está aquí:
https://computing.llnl.gov/tutorials/pthreads/
Me gusta que esté escrito sobre la API, en lugar de sobre una implementación particular, y ofrece algunos ejemplos simples y agradables para ayudarlo a comprender la sincronización.
fuente
Me topé con esta publicación recientemente y creo que necesita una solución actualizada para el mutex c ++ 11 de la biblioteca estándar (es decir, std :: mutex).
Pegué un código a continuación (mis primeros pasos con un mutex: aprendí la concurrencia en win32 con HANDLE, SetEvent, WaitForMultipleObjects, etc.).
Como es mi primer intento con std :: mutex y sus amigos, ¡me encantaría ver comentarios, sugerencias y mejoras!
fuente
La función
pthread_mutex_lock()
o bien adquiere el mutex para el subproceso de llamada o bloquea el hilo hasta que el mutex puede ser adquirida. El relacionadopthread_mutex_unlock()
lanza el mutex.Piense en el mutex como una cola; cada hilo que intente adquirir el mutex se colocará al final de la cola. Cuando un subproceso libera el mutex, el siguiente subproceso de la cola se sale y ahora se está ejecutando.
Una sección crítica se refiere a una región de código donde es posible el no determinismo. A menudo esto se debe a que varios subprocesos intentan acceder a una variable compartida. La sección crítica no es segura hasta que se establezca algún tipo de sincronización. Un bloqueo de mutex es una forma de sincronización.
fuente
Se supone que debe verificar la variable mutex antes de usar el área protegida por el mutex. Por lo tanto, su pthread_mutex_lock () podría (dependiendo de la implementación) esperar hasta que se libere mutex1 o devolver un valor que indique que no se pudo obtener el bloqueo si otra persona ya lo ha bloqueado.
Mutex es realmente solo un semáforo simplificado. Si lees sobre ellos y los entiendes, entiendes mutexes. Hay varias preguntas sobre mutexes y semáforos en SO. Diferencia entre semáforo binario y mutex , cuándo debemos usar mutex y cuándo debemos usar semáforo, etc. El ejemplo del inodoro en el primer enlace es tan bueno como uno puede pensar. Todo lo que hace el código es verificar si la clave está disponible y, si lo está, la reserva. Tenga en cuenta que realmente no reserva el inodoro en sí, sino la llave.
fuente
pthread_mutex_lock
no puede regresar si alguien más tiene la cerradura. Se bloquea en este caso y ese es el punto.pthread_mutex_trylock
es la función que volverá si se mantiene el bloqueo.Para aquellos que buscan el ejemplo shortex mutex:
fuente
EJEMPLO DE SEMÁFORO ::
Referencia: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
fuente