Гонка между виртуальной функцией и pthread_create - PullRequest
1 голос
/ 26 ноября 2009

Когда я пытаюсь создать экземпляр класса с помощью виртуального метода и передать его в pthread_create, я получаю условие состязания, в результате чего вызывающий иногда вызывает базовый метод вместо производного метода, как это и должно быть. После поиска в Google pthread vtable race я обнаружил, что это довольно известное поведение. У меня вопрос, как это можно обойти?

Код ниже демонстрирует это поведение при любой настройке оптимизации. Обратите внимание, что объект MyThread полностью создается перед передачей в 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;
}

Ответы [ 2 ]

5 голосов
/ 26 ноября 2009

Единственная проблема здесь состоит в том, что вы удаляете объекты до того, как порожденный поток выполнит метод, поэтому к тому времени дочерний деструктор уже сработал, и объект больше не существует.

Так что это не имеет ничего общего с pthread_create или чем-то еще, это ваше время, вы не можете создать поток, дать ему некоторые ресурсы и удалить их, прежде чем он сможет использовать их.

Попробуйте, это покажет, как объекты уничтожаются основным потоком, прежде чем порожденный поток использует их:

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

};

С другой стороны, если вы просто поместите спящий режим в main перед удалением, у вас никогда не возникнет этой проблемы, потому что порожденный поток использует допустимые ресурсы.

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;
}
2 голосов
/ 26 ноября 2009

Не делайте pthread_join (или любую другую реальную работу) в деструкторе. Добавьте метод join () в Thread и вызовите его перед удалением thread [i] в ​​main.

Если вы попытаетесь вызвать pthread_join в деструкторе, поток может все еще выполняться Thread :: рутина (). Это означает, что он использует объект, который уже частично уничтожен . Что случится? Кто знает? Надеемся, что программа быстро вылетит.


Дополнительно:

  • Если вы хотите наследовать от Thread, Thread :: ~ Thread должен быть объявлен виртуальным.

  • Проверьте все ошибки и исправьте их (что, кстати, нельзя сделать внутри деструктора).

...