2010-03-10 7 views
45

Entiendo que el mutex recursivo permite que el mutex se bloquee más de una vez sin llegar a un punto muerto y debe desbloquearse la misma cantidad de veces. Pero, ¿en qué situaciones específicas necesitas usar un mutex recursivo? Estoy buscando situaciones de diseño/nivel de código. AccesoCuándo usar mutex recursivo?

+5

No es en realidad un engaño, sino que se superpone: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex. Esa pregunta pregunta "¿por qué alguien querría usar un mutex no recursivo?". Esta pregunta pregunta "¿por qué alguien querría usar un mutex recursivo?". Es como civilizaciones extraterrestres colisionando ;-) –

+0

@Steve: Sí, revisé ese hilo antes, pero simplemente no obtuve la respuesta que estaba buscando ... En realidad estoy buscando diseños específicos donde realmente se necesita. – jasonline

+1

Estoy bastante seguro de que no hay ninguno, o al menos ninguno es simple y, obviamente, tiene un buen diseño lo suficiente como para servir como aplicaciones asesinas. Cualquier función que tome un mutex puede ser reemplazada por dos funciones, una que la toma y otra que no. Cualquier función que quiera llamar a dicha función puede llamar a la apropiada según si el mutex en cuestión ya está retenido. Cualquier diseño en el que llame a funciones sin saber si ya tiene o no un mutex que sea relevante para esa función, probablemente esté roto. Pero mira casi cualquier Java para ver el código que gana brevedad del bloqueo recursivo. –

Respuesta

38

Por ejemplo cuando se tiene función que llama de forma recursiva, y desea sincronizarse a ella:

void foo() { 
    ... mutex_acquire(); 
    ... foo(); 
    ... mutex_release(); 
} 

sin un mutex recursivo que tendría que crear una función de "punto de entrada" en primer lugar, y esto se vuelve engorroso cuando tienes un conjunto de funciones mutuamente recursivas. Sin mutex recursivo:

void foo_entry() { 
    mutex_acquire(); foo(); mutex_release(); } 

void foo() { ... foo(); ... } 
+4

Si bien eso es cierto, bloquear tiene una gran cantidad de sobrecarga, por lo que no sería una mala idea crear versiones de subprocesos inseguros del código, primero, y luego crear un envoltorio de seguridad ligero para ellos. –

+3

@Michael: si su implementación mutex admite bloqueo recursivo, es probable que lo soporte de manera eficiente. Todo lo que generalmente se requiere es que la función de bloqueo haga "if (mutex.owner == thisthread) {++ lockcount; return;}". Si no tiene el bloqueo, está leyendo el campo del propietario no sincronizado, pero siempre que la implementación sepa que las lecturas y escrituras del campo mutex.owner son atómicas (por ejemplo, lecturas de palabras en x86), no puede obtener un mensaje falso positivo. Si tienes el candado, entonces no puedes obtener un falso negativo porque nada puede cambiar al propietario mientras lo tienes. –

+0

"no se puede obtener un falso positivo".Probablemente debería mencionar que hay algunas otras suposiciones allí, principalmente que cada vez que un hilo adquiere o libera un mutex, el kernel asegura que el campo mutex.owner se modifique de manera que sea visible para ese hilo (o cámbielo del enhebrar o eliminar el caché del subproceso, o lo que sea). –

1

Sin duda, sería un problema si un hilo bloqueado tratar de adquirir (de nuevo) un mutex ya poseía ...

¿Hay una razón para no permitir un mutex para ser adquirido varias veces por el mismo hilo?

+1

Simplicidad de implementación, creo. Además, he aquí un factor razonable contra los mutex recursivos: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex/293646#293646. Sospecho que Java puede haber prácticamente borrado la familiaridad del programador promedio con la capacidad de usar bloqueos no recursivos. –

+0

C++ está diseñado para ser un lenguaje muy rápido. A diferencia de otros lenguajes populares, C++ intenta proporcionar implementaciones simples (como std :: mutex) que hacen su trabajo fundamental lo más rápido posible. Si necesita usar el objeto más destacado (como std :: recursive_mutex) a un pequeño costo de gastos generales, también está disponible. El punto es darle al desarrollador la capacidad de escribir código increíblemente rápido cuando lo necesiten. –

19

Los mutexes recursivos y no recursivos tienen casos de uso diferentes. Ningún tipo de mutex puede reemplazar fácilmente al otro. Los mutexes no recursivos tienen menos sobrecarga, y los mutex recursivos tienen en algunas situaciones semánticas útiles o incluso necesarias y en otras situaciones semánticas peligrosas o incluso rotas. En la mayoría de los casos, alguien puede reemplazar cualquier estrategia utilizando mutexes recursivos con una estrategia diferente más segura y más eficiente basada en el uso de mutexes no recursivos.

  • Si sólo desea excluir otros temas la utilización de su recurso protegido mutex, entonces se podría usar cualquier mutex tipo, pero puede ser que desee utilizar el mutex no recursivo debido a su cabeza más pequeña.
  • Si desea llamar a funciones de forma recursiva, que bloquee la misma exclusión mutua, a continuación, o bien
    • tienen que utilizar un mutex recursivo o
    • han bloquear y desbloquear el mismo mutex no recursiva de nuevo y otra vez (¡cuidado con los hilos concurrentes!) (suponiendo que esto sea semánticamente correcto, podría ser un problema de rendimiento), o
    • tienen que anotar de algún modo qué mutexes ya bloquearon (simulando propiedad recursiva/mutexes).
  • Si desea bloquear varios objetos mutex-protegido de un conjunto de tales objetos, donde los conjuntos podrían haber sido construidos mediante la fusión, se puede elegir
    • de usar por objeto precisamente un mutex , lo que permite más hilos para trabajar en paralelo, o
    • a utilizar por objeto una referencia a cualquier posiblemente compartido mutex recursivo, para disminuir la probabilidad de no bloquear todos mutex it juntos, o
    • de usar por objeto una referencia comparable a cualquier posiblemente compartido mutex no recursivo, eludiendo la intención de bloquear varias veces.
  • Si desea quitar un bloqueo en un hilo diferente de lo que se ha bloqueado, entonces usted tiene que utilizar bloqueos no recursivos (o bloqueos recursivos que permiten explícitamente esta vez de lanzar excepciones).
  • Si desea utilizar las variables de sincronización , entonces usted necesita para ser capaz de desbloquear explícitamente la exclusión mutua mientras espera en cualquier variable de sincronización, de manera que se permite ser utilizado en otros hilos del recurso. Eso solo es posible de manera sensata con mutexes no recursivos, ya que los repetidores recursivos ya podrían haber sido bloqueados por la persona que llama de la función actual.
+4

No estoy seguro de que estoy de acuerdo en que "tener que desbloquear y bloquear el mismo mutex no recursivo una y otra vez (¡cuidado con los hilos concurrentes!)" Es viable, incluso con la advertencia. Si no está listo para liberar el recurso (o está en un estado incoherente o no quiere que nadie más lo use), _no_ desbloquee/repita/bloquee. Te garantizo que en algún momento otro hilo se colará y te morderá donde el sol no brille :-) No declinaré ya que el resto de la respuesta es muy buena, solo pensé en mencionarlo. – paxdiablo

+1

Consulte http://stackoverflow.com/questions/10546867/why-would-we-want-to-make-function-recursive-which-has-a-mutex-lock para saber qué lo compró. – paxdiablo

+1

y aquí también: http://stackoverflow.com/q/10548284/462608 –

3

Si desea ver un ejemplo de código que usa mutex recursivas, consulte las fuentes de "Electric Fence" para Linux/Unix. 'Fue una de las herramientas comunes de Unix para encontrar "límites comprobando" los excesos y errores de lectura/escritura, así como el uso de la memoria que se ha liberado antes de que apareciera el Valgrind.

Simplemente compile y vincule la cerca eléctrica con las fuentes (opción -g con gcc/g ++), y luego vincule con su software con la opción de enlace -lefence, y comience a recorrer las llamadas a malloc/free. http://elinux.org/Electric_Fence

2

me encontré con la necesidad de un mutex recursivo hoy, y creo que es quizá el ejemplo más simple entre las respuestas publicadas hasta ahora: Esta es una clase que expone dos funciones de la API, el proceso (...) y RESET().

public void Process(...) 
{ 
    acquire_mutex(mMutex); 
    // Heavy processing 
    ... 
    reset(); 
    ... 
    release_mutex(mMutex); 
} 

public void reset() 
{ 
    acquire_mutex(mMutex); 
    // Reset 
    ... 
    release_mutex(mMutex); 
} 

Ambas funciones no deben ejecutarse simultáneamente porque modifican partes internas de la clase, así que quería utilizar un mutex. El problema es que Process() llama a reset() internamente, y crearía un interbloqueo porque mMutex ya está adquirido. Bloquearlos con un bloqueo recursivo soluciona el problema en su lugar.

+3

Simplemente haga una versión privada de 'reset()' que no bloqueará el mutex para uso interno. Public API 'reset()' para bloquear el mutex y llamar al restablecimiento interno y desbloquear el mutex. Esta es una razón ridícula para introducir mutexes recursivos. Para maximizar el paralelismo debe mantener el bloqueo lo menos posible, los mutexes recursivos no ayudan con esto, al contrario. – FooF

+1

El tiempo que uno se aferra a un mutex depende en gran medida de la tarea en cuestión.Tal vez solo codifique la GUI; otras personas codifican elementos de procesamiento pesados ​​que necesitan mutexed. –

+0

Los mutexes son asesinos de concurrencia, ese es el objetivo de ellos. Tan pronto como sus invarianzas estén en buen estado, se desbloquea para que otros hilos de procesamiento pesados ​​puedan hacer su trabajo. Vea al gran David Butenhof hablando de mutexes recursivos: http://www.zaval.org/resources/library/butenhof1.html (solo deben usarse como una "muleta" hasta que obtenga un código no seguro para subprocesos refactorizado en thread-safe estado con bloqueos específicos, también piensa que en este caso solo necesita un bloqueo global único). – FooF

Cuestiones relacionadas