Corrida entre função virtual e pthread_create
-
05-07-2019 - |
Pergunta
Quando tento criar uma instância de classe com um método virtual e passá-lo para pthread_create, recebo uma condição de corrida, fazendo com que o chamador, por vezes, chamar o método base, em vez do método derivado como deveria. Após googling pthread vtable race
, eu descobri que este é um comportamento bastante conhecido. A minha pergunta é, o que é uma boa maneira de contornar isso?
O código a seguir exibe este comportamento em qualquer configuração de otimização. Note-se que o objeto MyThread é completamente construído antes de ser passado para 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;
}
Solução
O único problema aqui é que você está excluindo os objetos antes que o segmento gerou executa o método, por isso, que o tempo o destruidor criança já demitido e o objeto não está mais lá.
Por isso, não tem nada a ver com pthread_create ou qualquer outra coisa, o seu o seu tempo, você não pode gerar um segmento, dar-lhe alguns recursos e excluí-los antes que ele tenha a chance de usá-los.
Tente isso, ele vai mostrar como os objs são destruídos por thread principal antes usos segmento gerado eles:
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 outro lado, se você só colocar um sono principal antes de excluí-los você nunca vai ter esse problema porque o segmento gerado está usando os 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;
}
Outras dicas
Não faça pthread_join (ou qualquer outro trabalho real) em destructor. Adicionar join () método de thread e chamá-lo antes fio de exclusão [i] na principal.
Se você tentar chamar pthread_join em destructor, rosca pode ser ainda execução Thread :: rotina (). O que significa que ele está usando objeto que é já parcialmente destruído . O que vai acontecer? Quem sabe? Esperemos programa irá falhar rapidamente.
Além disso:
-
Se desejar para herdar de Thread, Thread :: ~ Tópico deve ser declarado virtual.
-
Verifique se todos os erros e tratá-los adequadamente (que BTW não pode ser feito dentro destructor).