En informática , el mutex reentrante (también conocido como mutex recursivo o bloqueo recursivo ) es una primitiva de sincronización que puede ser bloqueada varias veces por el mismo hilo sin causar un interbloqueo .
Si bien un hilo que intenta bloquear un mutex estándar (no reentrante) que ya posee se bloquearía indefinidamente, esta operación tiene éxito en un mutex reentrante. Esto se logra asociando el mutex con el hilo propietario y manteniendo un contador de bloqueos. El hilo propietario puede adquirir el bloqueo varias veces, incrementando el contador en cada ocasión. El bloqueo solo se libera para que otros hilos lo adquieran una vez que el hilo propietario lo ha desbloqueado el mismo número de veces que lo adquirió, lo que reduce el contador a cero.
Motivación
Un mutex reentrante resuelve los interbloqueos que pueden ocurrir cuando una función necesita adquirir un bloqueo que ya está en posesión del mismo hilo. Esto suele suceder en código recursivo o cuando una función que adquiere un bloqueo llama a otra función que debe adquirir el mismo bloqueo. [ 1 ]
Los mutex recursivos resuelven el problema de la no reentrada con los mutex regulares: si una función que toma un bloqueo y ejecuta una devolución de llamada es a su vez llamada por la devolución de llamada, se produce un interbloqueo . [ 2 ] En pseudocódigo , esa es la siguiente situación:
Considere el siguiente escenario en pseudocódigo :
var m : Mutex // Un mutex estándar, no reentrante, inicialmente desbloqueado. función lock_and_call(i : Entero) m.lock() devolución de llamada(i) m.desbloquear() función callback(i : Entero) si i > 0 bloquear_y_llamar(i - 1) lock_and_call(1) // Invocando la función
Cuando se ejecuta lock_and_call(1) con un mutex estándar, se produce un interbloqueo:
- La llamada inicial a lock_and_call(1) adquiere con éxito el bloqueo m .
- Luego llama a callback(1) .
- Dentro de callback(1) , como i > 0 , llama a lock_and_call(0) .
- Esta segunda llamada a lock_and_call intenta adquirir el bloqueo m nuevamente.
- Interbloqueo : Dado que el mutex m ya está bloqueado, el hilo se detiene y espera a que se libere el bloqueo. Sin embargo, es el propio hilo el que mantiene el bloqueo, por lo que espera a completar una acción que nunca podrá realizar.
El uso de un mutex reentrante para m evita este interbloqueo. Cuando la segunda llamada a lock_and_call(0) intenta bloquear el mutex, la operación se realiza correctamente porque el hilo que intenta adquirir el bloqueo ya es el propietario. El contador interno del mutex se incrementa. El bloqueo solo se libera por completo cuando ambas llamadas a lock_and_call han finalizado y realizado sus operaciones m.unlock() correspondientes.
Uso práctico
W. Richard Stevens señala que los bloqueos recursivos son "complicados" de usar correctamente y recomienda su uso para adaptar código de un solo hilo sin cambiar las API , pero "solo cuando no hay otra solución posible". [ 3 ]
El mecanismo de sincronización nativo del lenguaje Java , `monitor` , utiliza bloqueos recursivos. Sintácticamente, un bloqueo es un bloque de código precedido por la palabra clave `synchronized` y entre paréntesis una referencia a un objeto que se utilizará como mutex. Dentro del bloque `synchronized`, el objeto dado puede utilizarse como variable de condición mediante las llamadas a `wait()`, `notify()` o `notifyAll()`. Por lo tanto, todos los objetos son a la vez mutexes recursivos y variables de condición . [ 4 ]
Ejemplo
- El hilo A llama a la función F, que adquiere un bloqueo reentrante para sí mismo antes de continuar.
- El hilo B llama a la función F, que intenta adquirir un bloqueo reentrante para sí mismo, pero no puede debido a que ya existe uno pendiente, lo que resulta en un bloqueo (espera) o en un tiempo de espera si se solicita.
- El hilo F del hilo A se llama a sí mismo recursivamente. Como ya posee el bloqueo, no se bloqueará (no habrá interbloqueo). Esta es la idea central de un mutex reentrante y lo que lo diferencia de un bloqueo convencional.
- El hilo F del hilo B sigue esperando, o ha detectado el tiempo de espera y lo ha solucionado.
- El hilo F de A termina y libera su(s) bloqueo(s).
- El hilo B puede ahora adquirir un bloqueo reentrante y continuar si aún estaba esperando.
Emulación de software
La emulación de software se puede realizar utilizando la siguiente estructura:
- Una condición de "control" mediante un candado normal.
- Identificador del propietario, único para cada hilo (por defecto vacío/no establecido).
- Recuento de adquisiciones (valor predeterminado: cero)
Adquisición
- Adquiera la condición de control.
- Si el propietario está definido y no el hilo actual, espere a que se notifique la condición de control (esto también libera la condición).
- Establezca el propietario al hilo actual. El identificador del propietario ya debería haberse borrado en este punto, a menos que el adquirente ya sea el propietario.
- Incrementar el contador de adquisiciones (siempre debería dar como resultado 1 para los nuevos propietarios).
- Libere la condición de control.
Liberar
- Adquiera la condición de control, afirmando que el propietario es quien libera.
- Disminuya el contador de adquisiciones, afirmando que el contador es mayor o igual que cero.
- Si el recuento de adquisiciones es cero, borre la información del propietario y notifique la condición de control.
- Libere la condición de control.
Referencias
- ↑ Buschmann, Frank; Henney, Kevlin; Schmidt, Douglas C. (2007). Pattern-Oriented Software Architecture, A Pattern Language for Distributed Computing . John Wiley & Sons. p. 374. ISBN 9780470065303.
- ↑ Buschmann, Frank; Henney, Kevlin; Schmidt, Douglas C. (2007). Pattern-Oriented Software Architecture, A Pattern Language for Distributed Computing . John Wiley & Sons. p. 374. ISBN 9780470065303.
- ↑ Stevens, W. Richard; Rago, Stephen A. (2013). Programación avanzada en el entorno UNIX . Addison-Wesley. pág. 434.
- ↑ David Hovemeyer. "Clase 17: Hilos de Java, sincronización" . CS 365 - Computación paralela y distribuida . Archivado del original el 16 de febrero de 2015. Consultado el 4 de junio de 2015 .
{{cite book}}:|work=ignorado ( ayuda )
- Control de concurrencia
- patrones de diseño de software
- Hilos (informática)