Domanda

Quando provo a creare un'istanza di classe con un metodo virtuale e lo passo a pthread_create, ottengo una condizione di competizione, facendo sì che a volte il chiamante chiami il metodo di base invece del metodo derivato come dovrebbe. Dopo aver cercato su Google pthread vtable race , ho scoperto che si tratta di un comportamento abbastanza noto. La mia domanda è: qual è un buon modo per aggirare il problema?

Il codice seguente mostra questo comportamento a qualsiasi impostazione di ottimizzazione. Si noti che l'oggetto MyThread è completamente costruito prima di essere passato 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;
}
È stato utile?

Soluzione

L'unico problema qui è che stai eliminando gli oggetti prima che il thread generato esegua il metodo, quindi a quel punto il distruttore figlio è già stato sparato e l'oggetto non è più lì.

Quindi non ha nulla a che fare con pthread_create o altro, è il tuo tempismo, non puoi generare un thread, dargli alcune risorse ed eliminarle prima che abbia la possibilità di usarle.

Prova questo, mostrerà come gli objs vengono distrutti dal thread principale prima che il thread generato li usi:

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;
}

};

D'altra parte, se metti un sonno in main prima di eliminarli, non avrai mai quel problema perché il thread generato utilizza risorse valide.

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;
}

Altri suggerimenti

Non eseguire pthread_join (o qualsiasi altro lavoro reale) in distruttore.  Aggiungi il metodo join () a Thread e chiamalo prima di eliminare il thread [i] in main.

Se provi a chiamare pthread_join in distruttore, il thread potrebbe essere ancora in esecuzione  Discussione :: di routine ().  Ciò significa che utilizza un oggetto già parzialmente distrutto .  Cosa accadrà? Chissà? Si spera che il programma si blocchi rapidamente.


In aggiunta:

  • Se desideri ereditare da Thread, Thread :: ~ Thread dovrebbe essere dichiarato virtuale.

  • Controlla tutti gli errori e gestiscili correttamente (che BTW non può essere fatto all'interno del distruttore).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top