2012-02-16 26 views
5

Soy nuevo en la programación de multihilo y estoy confundido acerca de cómo funciona Mutex. En el manual Boost :: Thread, indica:Boost, concepto mutex

Los mutexes garantizan que solo un hilo puede bloquear un mutex dado. Si una sección de código está rodeada por un bloqueo y desbloqueo de exclusión mutua, se garantiza que solo un subproceso a la vez ejecuta esa sección de código. Cuando ese hilo desbloquea el mutex, otros hilos pueden entrar a esa región código:

Mi entendimiento es que mutex se utiliza para proteger una sección de código sea ejecutado por varios subprocesos al mismo tiempo, NO proteger el dirección de memoria de una variable. Es difícil para mí captar el concepto, lo que sucede si tengo 2 funciones diferentes tratando de escribir en la misma dirección de memoria.

¿Existe algo como esto en la biblioteca Boost:

  1. bloquear una dirección de memoria de una variable, por ejemplo, dobles x, bloqueo (x); Entonces que otros hilos con una función diferente no pueden escribir en x.
  2. hacer algo con x, por ejemplo, x = x + rand();
  3. de desbloqueo (x)

Gracias.

+1

Creo que podría estar interesado en [memoria transaccional de software] (http://en.wikipedia.org/wiki/Software_transactional_memory), pero eso no existe (de forma nativa) en C++. –

Respuesta

5

El mutex en sí solo garantiza que solo un hilo de ejecución pueda bloquear el mutex en cualquier momento. Depende de usted asegurarse de que la modificación de la variable asociada solo ocurra mientras el mutex está bloqueado.

C++ le da una manera de hacerlo un poco más fácil que en algo como C.En C, le corresponde a usted escribir el código correctamente, asegurándose de que en cualquier lugar que modifique la variable, primero bloquea el mutex (y, por supuesto, lo desbloquea cuando termina).

En C++, que es bastante fácil para encapsular todo en una clase con un poco de sobrecarga de operadores:

class protected_int { 
    int value; // this is the value we're going to share between threads 
    mutex m; 
public: 
    operator int() { return value; } // we'll assume no lock needed to read 
    protected_int &operator=(int new_value) { 
     lock(m); 
     value = new_value; 
     unlock(m); 
     return *this; 
    } 
}; 

Obviamente estoy simplificando que una gran cantidad (hasta el punto de que es probable que sea inútil en su forma actual), pero es de esperar que entiendas la idea, que es que la mayoría del código solo trata el objeto protected_int como si fuera una variable normal.

Cuando lo hace, sin embargo, el mutex se bloquea automáticamente cada vez que le asigna un valor y se desbloquea inmediatamente después. Por supuesto, ese es el caso más simple posible: en muchos casos, debe hacer algo como bloquear el mutex, modificar dos (o más) variables al unísono y luego desbloquearlas. Independientemente de la complejidad, sin embargo, la idea sigue siendo que centralice todo el código que realiza la modificación en un solo lugar, para que no tenga que preocuparse por bloquear el mutex en el resto del código. Si tiene dos o más variables juntas, generalmente tendrá que bloquear el mutex para leer, no solo para escribir; de lo contrario, puede obtener un valor incorrecto fácilmente cuando una de las variables se haya modificado, pero la otra no. t.

1

Para proteger una dirección de memoria compartida por múltiples hilos en dos funciones diferentes, ambas funciones tienen que usar el mismo mutex ... de lo contrario, se encontrará con un escenario donde los hilos de cualquiera de las funciones pueden acceder indiscriminadamente al mismo región de memoria "protegida".

Así que boost::mutex funciona bien para el escenario que describe, pero solo tiene que asegurarse de que para un recurso determinado que está protegiendo, todas las rutas a ese recurso bloquean exactamente la misma instancia del objeto boost::mutex.

3

No, no hay nada en el impulso (o en otro lugar) que bloqueará la memoria de esa manera. Debe proteger el código que accede a la memoria que desea proteger.

qué pasa si tengo 2 funciones diferentes tratando de escribir en la misma dirección de memoria .

Suponiendo que quiere decir 2 funciones que se ejecutan en diferentes hilos, ambas funciones deben bloquear el mutex misma, por lo que sólo uno de los hilos pueden escribir a la variable en un momento dado.

Cualquier otro código que tenga acceso (ya sea que lea o escriba) la misma variable también tendrá que bloquear el mismo mutex; el no hacerlo dará como resultado un comportamiento indeterminista.

0

Su comprensión es correcta con respecto a los mutexes. Protegen la sección de código entre el bloqueo y el desbloqueo.

Según lo que sucede cuando dos hilos escriben en la misma ubicación de la memoria, se serializan. Un hilo escribe su valor, el otro hilo lo escribe. El problema con esto es que no se sabe qué hilo se escribirá primero (o último), por lo que el código no es determinista.

Finalmente, para proteger una variable en sí misma, puede encontrar un concepto cercano en variables atómicas. Las variables atómicas son variables que están protegidas por el compilador o el hardware, y pueden modificarse atómicamente. Es decir, las tres fases que comentas (leer, modificar, escribir) ocurren atómicamente. Eche un vistazo a Boost atomic_count.

1

Creo que el detalle que te falta es que una "sección de código" es una sección arbitraria de código. Puede ser dos funciones, la mitad de una función, una sola línea o lo que sea.

Así las porciones de sus 2 funciones diferentes que tienen el mismo mutex cuando tienen acceso a los datos compartidos, son "una sección de código rodeado por un bloqueo de exclusión mutua y desbloquear" así, por tanto, "está garantizado que sólo un hilo a una el tiempo ejecuta esa sección de código ".

Además, esto explica una propiedad de los mutexes. No afirma que esta es la única propiedad que tienen.

2

Es posible hacer operaciones atómicas sin bloqueo en ciertos tipos usando Boost.Atomic. Estas operaciones no son bloqueantes y generalmente son mucho más rápidas que un mutex. Por ejemplo, para añadir algo de forma atómica que puede hacer:

boost::atomic<int> n = 10; 
n.fetch_add(5, boost:memory_order_acq_rel); 

Este código añade atómicamente 5 a n.