Гонка между виртуальной функцией и pthread_create
-
05-07-2019 - |
Вопрос
Когда я пытаюсь создать экземпляр класса с помощью виртуального метода и передать его в 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;
}
Решение
Единственная проблема здесь в том, что вы удаляете объекты до того, как порожденный поток выполнит метод, поэтому к тому времени дочерний деструктор уже сработал, и объект больше не существует. Р>
Так что это не имеет ничего общего с 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;
}
Другие советы
Не выполняйте pthread_join (или любую другую реальную работу) в деструкторе.Добавьте метод join() в Thread и вызывайте его перед удалением потока[i] в файле main.
Если вы попытаетесь вызвать Pthread_join в Destructor, поток может все еще выполнять Thread :: Pronatine ().Это означает, что он использует объект, который уже частично разрушен.Что случится?Кто знает?Надеюсь, программа быстро выйдет из строя.
Кроме того:
Если вы хотите наследовать от Thread, Thread::~Thread следует объявить виртуальным.
Проверьте все ошибки и обработайте их правильно (что, кстати, невозможно сделать внутри деструктора).