Pregunta

Cuando intento crear una instancia de clase con un método virtual y pasarla a pthread_create, obtengo una condición de carrera, lo que hace que la persona que llama a veces llame al método base en lugar del método derivado como debería. Después de googlear pthread vtable race , descubrí que este es un comportamiento bastante conocido. Mi pregunta es, ¿cuál es una buena manera de evitarlo?

El código siguiente muestra este comportamiento en cualquier configuración de optimización. Tenga en cuenta que el objeto MyThread está completamente construido antes de pasarlo a pthread_create.

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Thread {
    pthread_t thread;

    void start() {
        int s = pthread_create(&thread, NULL, callback, this);
        if (s) {
            fprintf(stderr, "pthread_create: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    static void *callback(void *ctx) {
        Thread *thread = static_cast<Thread*> (ctx);
        thread->routine();
        return NULL;
    }
    ~Thread() {
        pthread_join(thread, NULL);
    }

    virtual void routine() {
        puts("Base");
    }
};

struct MyThread : public Thread {
    virtual void routine() {

    }
};

int main() {
    const int count = 20;
    int loop = 1000;

    while (loop--) {
        MyThread *thread[count];
        int i;
        for (i=0; i<count; i++) {
            thread[i] = new MyThread;
            thread[i]->start();
        }
        for (i=0; i<count; i++)
            delete thread[i];
    }

    return 0;
}
¿Fue útil?

Solución

El único problema aquí es que está eliminando los objetos antes de que el subproceso generado ejecute el método, por lo que en ese momento el destructor secundario ya se disparó y el objeto ya no está allí.

Por lo tanto, no tiene nada que ver con pthread_create o lo que sea, es tu momento, no puedes generar un hilo, darle algunos recursos y eliminarlos antes de que tenga la oportunidad de usarlos.

Intenta esto, mostrará cómo los objetos se destruyen por el hilo principal antes de que el hilo generado los use:

struct Thread {
pthread_t thread;
bool deleted;

void start() {
    deleted=false;
    int s = pthread_create(&thread, NULL, callback, this);
    if (s) {
            fprintf(stderr, "pthread_create: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
    }
}
static void *callback(void *ctx) {
    Thread *thread = static_cast<Thread*> (ctx);
    thread->routine();
    return NULL;
}
~Thread() {
    pthread_join(thread, NULL);
}

virtual void routine() {
    if(deleted){
        puts("My child deleted me");
    }
    puts("Base");
}
};

struct MyThread : public Thread {
virtual void routine() {

}
~MyThread(){
    deleted=true;
}

};

Por otra parte, si solo pones un estado de suspensión en main antes de eliminarlos, nunca tendrás ese problema porque el subproceso generado está usando recursos válidos.

int main() {
const int count = 20;
int loop = 1000;

while (loop--) {
    MyThread *thread[count];
    int i;
    for (i=0; i<count; i++) {
            thread[i] = new MyThread;
            thread[i]->start();
    }
    sleep(1);
    for (i=0; i<count; i++)
            delete thread[i];
}

return 0;
}

Otros consejos

No hagas pthread_join (o cualquier otro trabajo real) en el destructor.  Agregue el método join () al subproceso y llámelo antes de eliminar el hilo [i] en main.

Si intenta llamar a pthread_join en el destructor, es posible que el hilo aún se esté ejecutando  Hilo :: rutina ().  Lo que significa que está utilizando un objeto que está ya parcialmente destruido .  ¿Lo que sucederá? ¿Quién sabe? Esperemos que el programa se bloquee rápidamente.


Además:

  • Si desea heredar de Thread, Thread :: ~ Thread debe declararse virtual.

  • Verifica todos los errores y los maneja adecuadamente (lo que por cierto no se puede hacer dentro del destructor).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top