2012-02-23 17 views
8

Digamos que tengo dos variables, protected_var1 y protected_var2. Supongamos además que estas variables se actualizan a través de varios subprocesos, y son bastante independientes, ya que generalmente se trabaja en una u otra, pero no en ambas, por lo que ambas tienen su propia protección mutex para la eficiencia.¿El orden de desbloqueo de mutexes hace una diferencia aquí?

Suponiendo:

-Siempre bloqueo mutex con el fin (mutex1 continuación mutex2) en mi código en las regiones donde se requieren las dos cerraduras.

-ambos mutexes se utilizan en muchos otros lugares por ellos mismos (como simplemente bloquear mutex1, o simplemente bloquear mutex2).

¿El orden en el que desbloqueo los mutexes al final de una función usando ambos hace una diferencia en esta situación?

void foo() 
{ 
    pthread_mutex_lock(&mutex1); 
    pthread_mutex_lock(&mutex2); 

    int x = protected_var1 + protected_var2; 

    pthread_mutex_unlock(&mutex1); //Does the order of the next two lines matter? 
    pthread_mutex_unlock(&mutex2); 
} 

Me hicieron una pregunta hace mucho tiempo en una entrevista con respecto a esta situación, y salí sintiendo que la respuesta era sí - el orden de esas dos desbloqueos sí importa. No puedo, por la vida de mi entender, cómo podría resultar un punto muerto si los bloqueos se obtienen siempre en el mismo orden donde se usen ambos.

Respuesta

7

No puedo entender cómo podría resultar un punto muerto si las cerraduras siempre se obtienen en el mismo orden donde se usen ambas.

En estas circunstancias, no creo que el orden de desbloqueo de los mutexes pueda ser la causa de un punto muerto.

Dado que pthread_mutex_unlock() no bloquea, ambos silenciosos siempre se desbloquearían independientemente del orden de las dos llamadas.

Tenga en cuenta que si intenta adquirir cerraduras entre las dos llamadas de desbloqueo, esto puede cambiar la imagen por completo.

0

No, no importa. Un punto muerto no puede ser el resultado de esto; ambos operadores de desbloqueo tienen garantizado el éxito (barra de corrupción del montón o problemas similares).

11

La orden no debería importar, siempre y cuando no intente adquirir otro bloqueo entre las versiones. Lo importante es siempre adquirir las cerraduras en el mismo orden; de lo contrario, corre el riesgo de un punto muerto.

EDIT:

Para ampliar la restricción: debe establecer una ordenación estricta entre las exclusiones mutuas, por ejemplo mutex1 precede mutex2 (pero esta regla es válida para cualquier cantidad de exclusión mutua). Solo puede solicitar un bloqueo en un mutex si no contiene un mutex que viene después en el pedido; p.ej. no puede solicitar un candado en mutex1 si mantiene un candado en mutex2. En cualquier momento estas reglas son respetadas, debe estar seguro. Con respecto a soltando, si suelta mutex1, luego intente readquirirlo antes de liberando mutex2, ha violado la regla.En este sentido, puede haber ventajas con respecto a una orden similar a una pila: la última adquisición es , siempre la primera publicada. Pero es una especie de efecto indirecto: la regla es que no puede solicitar un bloqueo en mutex1 si tiene uno en mutex2. Independientemente de si tenía un candado en mutex1 cuando adquirió el candado en mutex2 o no.

+2

Muy ** muy ** punto importante. Sabía que había una razón para desbloquear pedidos. Por favor amplía esto un poco para que la gente lo vea. Considera desbloquear 'mutex1', hacer algo y luego agarrar' mutex1' de nuevo. Recuerdo haber tenido un punto muerto que se resolvió ordenando de forma inversa el lanzamiento del mutex y esa fue la causa. –

+0

@ D.Shawley Eso ** causaría un punto muerto. (Me parece recordar analizar esto con más detalle en una respuesta reciente, a una pregunta sobre el uso de un mutex en un contenedor _y_ en los elementos individuales en el contenedor.) Pero eso viola la restricción de ordenamiento: si adquiere ambos mutexes, debe adquiere 'mutex1' primero. –

0

El orden de desbloqueo no es un problema aquí, pero el orden de bloqueo puede ser un problema.

considerar:

void foo() 
{ 
    pthread_mutex_lock(&mutex1); 
    pthread_mutex_lock(&mutex2); 

    int x = protected_var1 + protected_var2; 

    pthread_mutex_unlock(&mutex1); 
    pthread_mutex_unlock(&mutex2); 
} 

void bar() 
{ 
    pthread_mutex_lock(&mutex2); 
    pthread_mutex_lock(&mutex1); 

    int x = protected_var1 + protected_var2; 

    pthread_mutex_unlock(&mutex1); 
    pthread_mutex_unlock(&mutex2); 
} 

Esto puede llevar a un punto muerto desde foo podría haber bloqueado mutex1 y ahora espera a que mutex2 mientras bar ha bloqueado mutex2 y ahora espera a mutex1. Por lo tanto, es una buena idea asegurarse de que los bloqueos mutex anidados siempre se bloqueen en el mismo orden.

0

Lo que puedo ver es que si otra operación se está llevando mutex2 y la mantiene durante mucho tiempo, su función foo() será pegado después pthread_mutex_lock(&mutex1); y probablemente tendrá algún impacto en el rendimiento.

0

Siempre que var1 y var2 estén bloqueados, se bloquean en el mismo orden de forma segura independientemente de la orden de liberación. De hecho, la liberación en el orden en que se bloquearon es el comportamiento de liberación de RAII que se encuentra en los bloqueos STL y BOOST.

3

No importa si el bloqueo es correcto. La razón es que, incluso suponiendo que haya otro subproceso esperando para bloquear mutex1 y luego mutex2, el peor caso es que se programa inmediatamente tan pronto como libera mutex1 (y adquiere mutex1). A continuación, bloquea la espera de mutex2, que el hilo sobre el que está preguntando se lanzará tan pronto como se programe de nuevo, y no hay razón para que no ocurra pronto (de inmediato, si estos son los únicos dos hilos en juego).

Por lo tanto, puede haber un pequeño costo en el rendimiento en esa situación exacta, en comparación con si lanzó mutex2 primero y por lo tanto solo hubo una operación de reprogramación. Sin embargo, nada de lo que normalmente esperarías predecir o preocuparte, está dentro de los límites de "la programación a menudo no es determinista".

Sin embargo, el orden en que suelte los bloqueos podría afectar la programación en general. Supongamos que hay dos hilos esperando su hilo, y uno de ellos está bloqueado en mutex1 mientras que el otro está bloqueado en mutex2. Puede ocurrir que cualquiera que sea el bloqueo que suelte primero, ese hilo se ejecute primero, simplemente porque su hilo ha sobrevivido a su bienvenida (consumido más que un segmento de tiempo completo) y, por lo tanto, se descodifica tan pronto como cualquier otra cosa se puede ejecutar. Pero eso no puede causar un error en un programa que de otra manera sería correcto: no está permitido confiar en en el hilo que se está programando tan pronto como se libere el primer bloqueo. Entonces, cualquier orden de esos dos subprocesos en espera, ambos ejecutados simultáneamente si tiene múltiples núcleos, o los dos alternando en un núcleo, deben ser igualmente seguros independientemente del orden en que suelte los bloqueos.

1

El orden de desbloqueo no puede causar interbloqueos. Sin embargo, si le doy la oportunidad, recomiendo desbloquearlos en orden de bloqueo inverso. Esto tiene un efecto insignificante en la ejecución del código. Sin embargo, los desarrolladores están acostumbrados a pensar en términos de alcances y alcances "cercanos" en orden inverso. Al verlos en orden inverso, simplemente piensas en abrir cerraduras.Esto me lleva al segundo punto, que es que, en la mayoría de los casos, es más seguro reemplazar las llamadas directas para bloquear y desbloquear con un guardia basado en la pila que las llame por usted. Hacerlo rinde el mayor grado de corrección para el menor esfuerzo mental, y también es seguro en presencia de excepciones (¡que realmente puede ensuciar con un desbloqueo manual)!

Un simple protector (hay muchos por ahí .. esto sólo sólo un rápido para liar su propia):

class StMutexLock 
{ 
    public: 
     StMutexLock(pthread_mutex_t* inMutex) 
     : mMutex(inMutex) 
     { 
      pthread_mutex_lock(mMutex); 
     } 

     ~StMutexUnlock() 
     { 
      pthread_mutex_unlock(mMutex); 
     } 
    private: 
     pthread_mutex_t* mMutex; 
} 

{ 
    StMutexLock lock2(&mutex2); 
    StMutexLock lock1(&mutex1); 

    int x = protected_var1 + protected_var2; 
    doProtectedVar1ThingThatCouldThrow(); // exceptions are no problem! 
    // no explicit unlock required. Destructors take care of everything 
} 
0
void * threadHandle (void *arg) 
{ 
    // Try to lock the first mutex... 
    pthread_mutex_lock (&mutex_1); 

    // Try to lock the second mutex... 
    while (pthread_mutex_trylock(&mutex_2) != 0) // Test if already locked 
    { 
     // Second mutex is locked by some other thread. Unlock the first mutex so that other threads won't starve or deadlock 
     pthread_mutex_unlock (&mutex_1); 

     // stall here 
     usleep (100); 

     // Try to lock the first mutex again 
     pthread_mutex_lock(&mutex_1); 
    } 

    // If you are here, that means both mutexes are locked by this thread 

    // Modify the global data 
    count++; 

    // Unlock both mutexes!!! 

    pthread_mutex_unlock (&mutex_1); 

    pthread_mutex_unlock (&mutex_2); 
} 

supongo que esto puede evitar el estancamiento

+0

Además del código, proporcione algunos detalles sobre por qué funciona su código y qué hizo para que funcione. – buczek

Cuestiones relacionadas