2009-03-14 19 views
15

que iba a través de un código heredado y encontré el siguiente fragmento:¿Los destructores deben ser seguros para la fabricación de hilos?

MyClass::~MyClass() 
{ 
    EnterCriticalSection(&cs); 

//Access Data Members, **NO Global** members are being accessed here 


    LeaveCriticalSection(&cs); 
} 

Me pregunto va a ayudar, por casualidad, para custodiar el destructor?

considere un escenario:

1. Thread1 - About to execute any of the member function which uses critical section 
2. Thread2- About to execute destructor. 

Si la orden de ejecución es 1 => 2, entonces podría funcionar. Pero, ¿y si el orden se invierte?

¿Es un problema de diseño?

Respuesta

34

El destructor debe llamarse cuando el objeto está en uso. Si se trata de una situación como esta, , necesita una corrección fundamental. Sin embargo, el destructor podría querer alterar otra cosa (que no está relacionada con la clase que se está destruyendo) y podría necesitar una sección crítica (por ejemplo, como disminuir un contador global ).

+0

¿Pero debería ser seguro para los hilos en sí mismo? ¿Existe la posibilidad de que una clase sea "destruida dos veces" en paralelo? –

+0

@ TomášZato: No. Si una clase (con un destructor no trivial) se "destruye dos veces", no importa si esto sucede de manera segura o no, es UB en cualquier caso. – MikeMB

+1

A pesar de la edad de la respuesta, no estoy de acuerdo. Se debe aplicar una sincronización si las variables miembro han sido modificadas por otro hilo que el hilo donde se llamará al dtor.Sin embargo, no necesita ser una "sección crítica". Lo que se requiere es que exista una relación "Sincronizaciones con" entre los dos hilos. Esto puede requerir barreras de memoria apropiadas en cualquier hilo, a menos que los hilos sean los mismos. – CouchDeveloper

3

Creo que tienes un problema más fundamental. No debería ser legal destruir el objeto en un subproceso mientras que otro subproceso sigue llamando funciones de miembro. Esto en sí mismo está mal.

Incluso si protege satisfactoriamente su destructor con secciones críticas, ¿qué sucede cuando el otro hilo comienza a ejecutar el resto de la función? Lo hará en un objeto eliminado que (según su ubicación de asignación) será memoria basura o simple un objeto no válido.

Necesita modificar su código para asegurarse de que el objeto no se destruya mientras está en uso.

3

Define "thread safe". Estas son posiblemente las dos palabras más incomprendidas en la informática moderna.

Pero si existe la posibilidad de que el destructor se ingrese dos veces a partir de dos subprocesos diferentes (como lo implica el uso de objetos de simcronización), su código está en doo-doo profundo. Los objetos que están eliminando el objeto sobre el que está preguntando deberían estar gestionando esto: es (probablemente) en ese nivel donde debe producirse la sincronización.

0

No hará la diferencia. Si, como dices, el orden de las llamadas se invierte, estás llamando a una función miembro sobre un objeto destruido y eso va a fallar. La sincronización no puede reparar ese error lógico (para empezar, la llamada a la función miembro intentaría adquirir un objeto de bloqueo que ha sido destruido).

4

Si accede a las variables globales que pueda necesitar hilo de seguridad, sí

por ejemplo. Mi clase "Ventana" se agrega a la lista "knownWindows" en el constructor y se elimina en el destructor. "knownWindows" necesita ser seguro para los hilos, por lo que ambos bloquean un mutex mientras lo hacen.

Por otro lado, si su destructor solo accede a los miembros del objeto que se destruye, tiene un problema de diseño.

+0

Esa es la respuesta correcta. –

0

I el segundo comentario de Neil ButterWorth. Absolutamente, las entidades responsables de eliminar y acceder a la clase myclass, deben tener un control sobre esto.

Esta sincronización comenzará en realidad desde el momento en que se crea el objeto de tipo MyClass.

2

He visto un caso con subprocesos ACE, donde el subproceso se ejecuta en un objeto ACE_Task_Base y el objeto se destruye desde otro subproceso. El destructor adquiere un bloqueo y notifica al hilo contenido, justo antes de esperar una condición. El hilo que se ejecuta en la señal ACE_Task_Base indica la condición en la salida y permite que el destructor completa y la primera salida de rosca:

class PeriodicThread : public ACE_Task_Base 
{ 
public: 
    PeriodicThread() : exit_(false), mutex_() 
    { 
    } 
    ~PeriodicThread() 
    { 
     mutex_.acquire(); 
     exit_ = true; 
     mutex_.release(); 
     wait(); // wait for running thread to exit 
    } 
    int svc() 
    { 
     mutex_.acquire(); 
     while (!exit_) { 
     mutex_.release(); 
     // perform periodic operation 
     mutex_.acquire(); 
     } 
     mutex_.release(); 
    } 
private: 
    bool exit_; 
    ACE_Thread_Mutex mutex_; 
}; 

En este código, el destructor deben utilizar técnicas de seguridad de rosca para garantizar que el objeto no es destruido antes del hilo que se está ejecutando svc() salidas.

0

Tus comentarios dicen "NO Global miembros están siendo accedidos aquí" así que supongo que no. Solo el hilo que creó un objeto debería destruirlo, entonces ¿de qué otro hilo lo protegerías?

Me gusta la creación y la destrucción ordenadas, donde solo un único objeto posee otro subobjeto, y cualquier otro objeto con una referencia a ese subobjeto es un descendiente más abajo en el árbol. Si alguno de esos sub-objetos representa hilos diferentes, entonces se habrá asegurado de completarlos antes de que la destrucción avance por el árbol.

Ejemplo:

  • main() crear el objeto A
    • objeto A contiene el objeto B
      • objeto B contiene objeto C
        • objeto C crea un subproceso que accede objetos A y B
        • objeto se ejecuta destructor de C, en espera de su rosca para terminar
      • objeto destructor de B corre
    • objeto destructor de A corre
  • principal() devuelve

Los destructores para los objetos A y B no necesitan pensar en los hilos, y el destructor del objeto C solo necesita implementar algún mecanismo de comunicación (esperando en un evento, por ejemplo) con el hilo que eligió para crearse a sí mismo.

Solo puede meterse en problemas si comienza a distribuir referencias (punteros) a sus objetos a hilos arbitrarios sin hacer un seguimiento de cuándo se crean y destruyen esos hilos, pero si lo hace, entonces debe utilizar la referencia contando, y si es así, ya es demasiado tarde cuando se llame al destructor. Si todavía hay una referencia a un objeto, entonces nadie debería haber intentado siquiera invocar su destructor.

Cuestiones relacionadas