2009-11-26 4 views
1

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

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

Просто из любопытства, почему вы объявляете эти «классы» как 'struct's? – mrduclaw

+1

struct означает одно и то же, за исключением того, что все члены общедоступны по умолчанию. –

+1

@mrduclaw: Не беспокойтесь. Это просто прихоть, которую я недавно наблюдал, чтобы использовать * struct * вместо * class *. Это пройдет. :-P –

ответ

5

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

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

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

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

}; 

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

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

Спасибо, вот и все! Хороший улов, и отличный ответ! –

+2

Да. Это затрудняет попадание в состояние гонки, потому что поток sleep (1) в примере завершится до удаления потока [i]. Но не невозможно. Вы по-прежнему можете получить ситуацию, когда процедура thread() не завершается (или даже запускается) до вызова деструктора. Поэтому он попытается использовать частично разрушенный объект, который вызовет проблему. Правильное решение - уничтожить объект только после того, как вызов pthread_join подтверждает завершение потока. –

+1

Это вопрос либо предоставления порождаемой нити достаточного количества времени, либо обеспечения того, что она уже выполнила свою работу перед удалением ресурсов, просто зависимость ресурсов. –

2

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

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


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

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

  • Проверьте все ошибки и обработайте их должным образом (что BTW не может быть сделано внутри деструктора).

+0

Извините. Я смешал обе версии во время написания ответа. Я удалил части, которые не применяются. –

+0

+1 для принятия соединения из деструктора базового класса. –

+0

Предполагая, что это сделано правильно, 'join' - это именно то, что нужно сделать в конструкторе. Говорить, что не делать никакой реальной работы в деструкторе, просто глупо. Он существует с определенной целью. Чтобы гарантировать выполнение определенных задач, не полагаясь на программиста, чтобы явно вызвать функции очистки. – jalf

 Смежные вопросы

  • Нет связанных вопросов^_^