Удаление указателя в разных местах приводит к разному поведению (сбой или нет)

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

В моем многопоточном приложении основной поток создает параметры и сохраняет их:

typedef struct {
    int parameter1;
    double parameter2;
    float* parameter3;
} jobParams;

typedef struct {
    int ID;
    void* params;
} jobData;

std::vector<jobData> jobs;

// main thread
for (int i = 0; i < nbJobs; ++i) {
    jobParams* p = new jobParams;
    // fill and store params

    jobData data;
    data.ID = i;
    data.params = p;

    jobs.push_back(data);
}

// start threads and wait for their execution

// delete parameters
for (int i = 0; i < jobs.size(); ++i) {
    delete jobs[i].params;
}

Затем каждый поток получает указатель на набор параметров и вызывает с ним функцию задания:

// thread (generic for any job function and any type of params)
jobData* job = main->getNextParams();
jobFunction(job->ID, job->params);

Все это принимает void* в качестве аргумента, чтобы иметь возможность использовать любую структуру для параметров, но затем функция задания возвращает его обратно к правильной структуре:

void* jobFunction(void* param) {
    jobParams* params = (jobParams*) param;
    // do stuff
    return 0;
}

Моя проблема в следующем: если я delete params в конце jobFunction(), он работает отлично. Однако я бы предпочел, чтобы об удалении заботились потоки или основной поток, чтобы мне не приходилось помнить об удалении параметров для каждого jobFunction(), который я пишу.

Если я попытаюсь выполнить delete params сразу после вызова jobFunction() в тредах или даже в основном потоке, убедившись, что все потоки выполнены (и, следовательно, параметры больше не нужны), я получаю ошибку повреждения кучи:

HEAP[prog]: указан неверный адрес для RtlFreeHeap (02E90000, 03C2EE38)

Я использую Visual Studio 2008 Pro и поэтому не могу использовать valgrind или другие инструменты *nix для отладки. Все обращения к основному потоку из «дочерних потоков» синхронизируются с помощью мьютекса, так что проблема не в том, что я дважды удаляю одни и те же параметры.

На самом деле, используя средство просмотра памяти VS, я знаю, что память, указанная указателем jobParams, не изменяется между концом jobFunction() и точкой, где я пытаюсь ее удалить (ни в основном потоке, ни в «дочерних потоках»). ").

Я добавил определение обеих структур, а также то, как я хотел бы удалить параметры.


person Wookai    schedule 19.08.2009    source источник
comment
Можем ли мы увидеть, как вы удаляете параметры задания? У вас не должно возникнуть проблем с выполнением того, что вы предлагаете (у меня никогда не было проблем с подобным). Также мы можем увидеть определение класса/структуры jobData?   -  person Goz    schedule 19.08.2009
comment
Я добавил оба. Если я просто удалю параметры в jobFunction(), это сработает, но я хотел бы сделать это в основных потоках, как показано.   -  person Wookai    schedule 19.08.2009
comment
Хитрый, все выглядит хорошо. Поскольку вы говорите, что проблема возникает, если вы пытаетесь удалить из основного потока, возможно, в коде присоединения есть какая-то ошибка. Можно ли показать часть этого кода в тексте вопроса?   -  person redtuna    schedule 19.08.2009
comment
Что вы подразумеваете под кодом присоединения? Код, в котором я жду завершения потоков?   -  person Wookai    schedule 19.08.2009
comment
Да, это то, что я имею в виду.   -  person redtuna    schedule 19.08.2009
comment
Соединение представляет собой простой механизм опроса: когда поток выполнен, он уменьшает значение в основном потоке (поточно-безопасным способом). Метод соединения зацикливается до тех пор, пока это значение не станет равным нулю (с небольшой паузой в каждом цикле).   -  person Wookai    schedule 19.08.2009


Ответы (3)


Просто как мысль .. вы можете попробовать

for (int i = 0; i < jobs.size(); ++i) {
    delete (jobParams*)jobs[i].params;
}

создание нового типа jobParams и последующее удаление void* может быть причиной ваших проблем.

Есть ли причина, по которой вы храните параметры как void * в jobData? Я бы сказал, что если вы хотите иметь разные типы jobParams, вам следует использовать иерархию наследования, а не слепо приводить к void *.

person Goz    schedule 19.08.2009
comment
Да, причина, по которой я использую void*, заключается в том, чтобы иметь возможность иметь различные параметры. Я мог бы использовать наследование, но я считаю решение void* менее сложным. - person Wookai; 19.08.2009
comment
и исправляет ли кастинг перед удалением вашу проблему? Потому что как ни посмотри на это, это не правильное решение. - person Goz; 19.08.2009
comment
Только что проверил его под VS2008, и упрощенная попытка не вызывает ошибки, о которой вы говорите... - person Goz; 19.08.2009
comment
это происходит, если я удаляю элемент дважды. Я думаю, нам нужно увидеть больше вашего кода, потому что определенно кажется, что вы освобождаете более одного раза. - person Goz; 19.08.2009
comment
принял этот ответ, поскольку вы были наиболее вовлечены. Я сделал это по-другому, наконец, я не мог найти причину такого поведения! Спасибо, в любом случае ! - person Wookai; 28.08.2009

Такая ошибка обычно означает, что у вас где-то происходит гонка данных. Правильно ли работает main->getNextParams(), даже если он вызывается сразу несколькими потоками? Если он дает одинаковые параметры обоим, у вас может быть двойной фри в ваших руках.

Также, вместо

jobFunction(jobData->ID, jobData->params);

Вы, вероятно, имели в виду

jobFunction(job->ID, job->params);
person redtuna    schedule 19.08.2009
comment
Да, getNextParams() является потокобезопасным. Я использую мьютекс, чтобы убедиться, что я не возвращаю одни и те же параметры дважды. В любом случае, если бы это была такая проблема, удаление параметров в jobFunction() тоже не сработало бы. - person Wookai; 19.08.2009
comment
В ПОРЯДКЕ. Я подумал, что это стоит проверить, потому что отладка, основанная на том, что проблема была в X, тогда моя программа должна была завершиться раньше, иногда подводит меня, поскольку неопределенное поведение иногда просто делает правильные вещи в любом случае: так что, насколько я знаю, двойное удаление может иногда работать и иногда нет по причинам, не зависящим от простых смертных вроде меня. - person redtuna; 19.08.2009
comment
Да, я определенно согласен. Я дважды проверил такую ​​​​проблему синхронизации, прежде чем искать где-либо еще. - person Wookai; 19.08.2009

Чтобы отладить его, вы можете добавить член deleted в класс jobParams и установить его в true вместо фактического удаления объекта. Затем проверьте флаг deleted в каждом методе jobParams и создайте исключение, если это правда. Затем посмотрите, где возникает исключение.

person jon-hanson    schedule 19.08.2009
comment
Это не совсем класс и все такое, но я понял вашу точку зрения. Я попробую это. - person Wookai; 19.08.2009
comment
Упс. Исправлена ​​функция JobFunction на jobParams. - person jon-hanson; 19.08.2009