2012-07-25 16 views
6

Creo que tengo un buen manejo de al menos los conceptos básicos de multi-threading en C++, pero nunca he podido obtener una respuesta clara al bloquear un mutex alrededor de los recursos compartidos en el constructor o el destructor. Tenía la impresión de que deberías bloquear ambos lugares, pero recientemente los compañeros de trabajo no estuvieron de acuerdo. Fingir la siguiente clase se accede por varios subprocesos:Bloqueo de recursos compartidos en constructor y destructor

class TestClass 
{ 
public: 

    TestClass(const float input) : 
     mMutex(), 
     mValueOne(1), 
     mValueTwo("Text") 
    { 
     //**Does the mutex need to be locked here? 
     mValueTwo.Set(input); 
     mValueOne = mValueTwo.Get(); 
    } 

    ~TestClass() 
    { 
    //Lock Here? 
    } 

    int GetValueOne() const 
    { 
     Lock(mMutex); 
     return mValueOne; 
    } 

    void SetValueOne(const int value) 
    { 
     Lock(mMutex); 
     mValueOne = value; 
    } 

    CustomType GetValueTwo() const 
    { 
     Lock(mMutex); 
     return mValueOne; 
    } 

    void SetValueTwo(const CustomType type) 
    { 
     Lock(mMutex); 
     mValueTwo = type; 
    } 

private: 

    Mutex mMutex; 
    int mValueOne; 
    CustomType mValueTwo; 
}; 

Por supuesto que todo debe ser seguro a través de la lista de inicialización, pero qué pasa con las cuentas al interior del constructor? En el destructor, ¿sería beneficioso hacer un bloqueo sin alcance y nunca desbloquear (básicamente, solo llamar a pthread_mutex_destroy)?

+3

Al decir que la 'clase' es utilizado entre múltiples hilos, supongo que quiere decir que un objeto de tipo TestClass puede ser utilizado en múltiples hilos. En ese caso, solo está creando un solo objeto, por lo que no debería necesitar el bloqueo en el constructor. Si ambos hilos están en el constructor al mismo tiempo, están haciendo 2 objetos separados. Tiene más sentido bloquear la construcción de objetos, para asegurarse de que (por ejemplo) mValueTwo no se utiliza antes de que el objeto haya terminado de construir. Destructor parece que debería bloquearse, para asegurarse de que no se acceda a los datos mientras se destruyen. – Rollie

+0

@Rollie Sí, quise decir que el objeto sería compartido. Entonces, si creo: – Brett

+1

@Rollie: el acceso a la clase mientras se está destruyendo es un error con la gestión de instancias de por vida: el programa ya está roto cuando esto sucede (si se puede acceder mientras se está destruyendo, también podría ocurrir después)) –

Respuesta

12

Los subprocesos múltiples no pueden construir el mismo objeto, ni se debe permitir que ningún subproceso use el objeto antes de que esté completamente construido. Por lo tanto, en un código razonable, la construcción sin bloqueo es segura.

La destrucción es una carcasa un poco más dura. Pero, una vez más, una gestión adecuada de la vida de su objeto puede garantizar que un objeto nunca se destruya cuando existe la posibilidad de que algunos hilos todavía lo usen.

Un puntero compartido puede ayudar a lograr esto, por ejemplo. :

  • construir el objeto en un cierto hilo
  • pasar punteros compartidos a cada hilo que necesita el acceso al objeto (incluyendo el hilo que lo construyó si es necesario)
  • el objeto se destruye cuando todas las discusiones ha lanzado el puntero compartido

Pero, obviamente, existen otros enfoques válidos. La clave es mantener los límites adecuados entre las tres etapas principales de la vida de un objeto: construcción, uso y destrucción. Nunca permita una superposición entre cualquiera de estas etapas.

+1

El uso de shared_ptr es, en general, insuficiente. Eso es porque el último hilo para soltar el shared_ptr ejecutará el destructor, pero ese hilo puede no ser el último hilo en haber modificado el objeto. Si el objeto contiene miembros de datos complejos, como un vector o mapa, y el destructor no adquiere y suelta un bloqueo, el destructor puede ver la memoria obsoleta y causar un bloqueo. –

+0

@MichiHenning: un hilo solo lanzará su puntero compartido * después * completó todas las operaciones en el objeto, que es un requisito muy razonable (si no obvio). Entonces, el bloqueo es innecesario. La memoria obsoleta es de hecho teóricamente posible, pero para solucionar eso, necesitas una barrera de memoria, no de bloqueo. (concedido, varias bibliotecas de threading populares incluyen una barrera con ciertas operaciones de bloqueo, así que tal vez eso es lo que quería decir) –

+0

Aquí está el escenario. Cree shared_ptr en el hilo A y pase el shared_ptr (enclavamiento correcto) al hilo B. El hilo A actualiza el estado de foo y suelta su puntero. Más tarde, el hilo B suelta su shared_ptr, lo que hace que el hilo B llame al destructor de foo. Si el hilo B no ha cruzado una barrera de memoria desde el hilo A último actualizado, el destructor de foo funcionará en datos obsoletos. Si foo contiene un miembro complejo de datos, como un mapa, puede causar un bloqueo. Tienes razón en que una barrera de memoria es lo que se necesita. Sucede que un mutex también crea una barrera. –

1

No tienen que estar bloqueados en el constructor, ya que la única forma en que cualquier persona externa puede acceder a esos datos en ese punto es si los pasa por el propio constructor (o realiza un comportamiento indefinido, como llamar un método virtual).

[Editar: pieza extraída sobre destructor, ya que como un comentario afirma con razón, tiene problemas más grandes si usted está tratando de acceder a los recursos de un objeto que podría estar muerto]

+1

Si otro hilo puede acceder al objeto mientras está siendo destruido, tiene un error y el bloqueo no ayudará (considere lo que sucede si el hilo destructor adquiere primero el mutex). Además, no veo qué logrará el ajuste del mutex en un 'shared_ptr'. – interjay

+0

Tiene razón, acepto que tiene un problema si hay alguna posibilidad de que esté accediendo a los datos de un objeto que está a punto de morir. La propuesta shared_ptr consistía en evitar una situación en la que el mutex se destruyera junto con el objeto, pero supongo que tendría que bloquear ese mutex antes de intentar acceder a cualquier elemento en ese objeto, por lo que esto no funciona si está dentro del objeto. Editaré mi respuesta. –

Cuestiones relacionadas