LINUX.ORG.RU

c++ graph multithreading

 ,


0

1

Пламенный куку!

В однопоточном режиме граф удаляется нормально. В многопоточном - крашимся. Но! В каждом потоке создается свой собственный граф. Вывел небольшой (не)рабочий пример.

item - лист графа, у него есть имя и дети. У двух листьев могут быть одинаковые дети. make() создает граф. Отдельно объекта для представления графа нет, удаление ячеек делается через метод deleteIt().

class item{
public:
    item(QString const &n, item *parent = 0) :name(n), del(false){
        if (parent != 0)
            parent->addChild(this);
    }

    void addChild(item *i){
        childs.push_back(i);
    }

    void deleteIt(){
        if (!del){
            del = true;
            while (childs.count() > 0){
                auto child = childs[0];
                childs.removeFirst();
                child->deleteIt();
            }
            delete this;
        }
    }

    QList<item*> childs;
    QString name;

    bool del;

protected:
    ~item(){}
};


item *make(){
    auto root = new item("init");
    auto step = new item("step 1", root);
    auto step2 = new item("step 2");
    step->addChild(step2);
    auto step3 = new item("step 3", step);
    step3->addChild(step2);
    return root;
}


int main(int argc, char **argv){
    QCoreApplication a(argc, argv);
    for (int i = 0; i < 5; i++){
        auto thread = std::thread([](){
            for (int i = 0; i < 10000; i++){
                qDebug() << i;
                auto it = make();
                it->deleteIt();
            }
        });
        thread.detach();
    }
    a.exec();
}

Падаем рандомно в deletIt при обращении к списку детей, в деструкторе и изредка в addChild. Иногда почти сразу, иногда на второй-третьей тысяче итераций. Если оставляем один поток или исключаем добавление одинакового ребенка к двум разным элементам графа, все работает нормально.

Может быть создание объекта для представления графа, который хранил бы его уникальные листья, помогло бы, но в первую очереь хочу понять причину краша. Есть какая-то зацепка на уровне интуиции, что в одном потоке удаляется объект, в другом создается новый по тому же адресу, и после этого что-то идет не так. Но полноценную картину падения выстроить не получается.

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

★★★★★

Не удаляешь ли уже удалённых? Если одна и та же вершина доступна из двух других вершин.

yoghurt ★★★★★
()
Ответ на: комментарий от ziemin

Похожий код проработал полгода. Этот не падал за несколько часов.

staseg ★★★★★
() автор топика
Ответ на: комментарий от ziemin

В каких местах выскакивал?

Падаем рандомно в deletIt при обращении к списку детей, в деструкторе и изредка в addChild.

В деструторе — при попытке удаления невалидной QString, в deleteIt — в кишках QList, в addChild получаем невалидный this, падаем опять же в потрохах QList.

Ну судя по всему во всех случаях в конечном счете имеем невалидный this.

staseg ★★★★★
() автор топика
Последнее исправление: staseg (всего исправлений: 3)
Ответ на: комментарий от ziemin

Будет падать на проходе по списку плюс в деструкторе и addChild, уже проверял. Список детей чистить в принципе вообще не обязательно, сделал для подстраховки.

staseg ★★★★★
() автор топика
Ответ на: комментарий от fmdw

Воспроизводится. В тех же местах.

staseg ★★★★★
() автор топика

childs

children, блеат!

А ты посмотрел в документацию Qt и убедился, что QList, qDebug() являются потокобезопасными?

Harald ★★★★★
()
Ответ на: комментарий от ziemin

Суть delete this в том, что элемент удаляет за собой весь связанный с ним граф, при этом не удаляя дважды одни и те же объекты, на которые ссылаются разные листья. Во всяком случае так это раньше работало в одном потоке очень долгое время. Как сделать это через delete child я что-то не соображу навскидку.

staseg ★★★★★
() автор топика

delete this;

а ты уверен, что деструктор потом не вызывается повторно?

Я в C++11 ещё не заглядывал, но вдруг

auto it = make();

превращает it в какой-нибудь умный указатель, который дёргает delete для объекта, на который указывает, в своём деструкторе

Harald ★★★★★
()
step->addChild(step2);
step3->addChild(step2);

del то присваивается к true, но потом весь this удаляется. Следовательно дальнейшая проверка удалённого this->del указателя есть UB и начинается удалять удалённое.

Dendy ★★★★★
()
Ответ на: комментарий от Harald

children, блеат!

Йопт, ну. Пора бы запомнить.

У меня QList не шарит данные между разными потоками. Со стандартными контейнерами все то же самое. qDebug() все время работал в многопоточном коде, этот макрос просто раскрывается в создание объекта на стеке. Где падает, я писал в ОП и чуть подробнее — в комментах.

staseg ★★★★★
() автор топика
Ответ на: комментарий от Dendy

А потоки здесь видимо при том, что они борятся за адреса в куче. При одном потоке просто совпадает, что в удалённом this->del остаётся true.

Dendy ★★★★★
()
Ответ на: комментарий от Dendy

Я в комментах где-то писал уже, что удаление step3 из make лишь слегка снижает скорость падения. Но мне все равно интересно, что ты имеешь в виду, пошагово, я не понял :)

staseg ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Да имеет он смысл. Мы ставим del в true, обходим граф, если находим этот же элемент — игнорим его. Только потом удаляем объект.

staseg ★★★★★
() автор топика

Я скорее всего пару дней не смогу отметиться в топике. Позже выложу решение, если найду. Если вы найдете проблему, буду благодарствовать :)

staseg ★★★★★
() автор топика
Ответ на: комментарий от staseg

Мы ставим del в true, обходим граф, если находим этот же элемент — игнорим его.

Эта проверка имеет смысл только если дочерний элемент пытается удалить родительский, что может быть только в зацикленном графе. В примере же граф ациклический. Сначала step удаляет step2, потом step3 удаляёт step2.

Dendy ★★★★★
()
Ответ на: комментарий от staseg

Просто вызывать деструктор там, где вызываешь deleteIt() и для того же элемента. Вытащить его наружу.

step2 у тебя удаляется дважды.

ziemin ★★
()

«child->deleteIt();» очень спорный момент в многопоточном приложении. Вообще то что приведено, это пример того где подсчёт ссылок в какой-то мере может спасти - удаляешь ссылку, удаляй только у себя, всем прочим можешь послать асинхронное сообщение и/или подправить атомарный счётчик - не более того.

лень лезть в документацию, но что-то подсказывает что и QList<item*> не является потокобезопасным.

Давно не брал я в руки шашек (это про Qt) но многопоточные обращения надо обрамлять мутексами или делать event-driven архитектуру. Что-то вы неправильно паралелитесь :-)

MKuznetsov ★★★★★
()

qDebug() удалить не пробовал? он тредсейфный?

pon4ik ★★★★★
()

тьфу ты. По треду уже ответили, но вообще ссзб.

if (!del)//значения выражения для удалённого обьекта будет зависеть от фазы луны и прочей мистики
//ибо:
delete this;//который как только ты таки попадешь на адресок 
//по которому байтик с нулём хранится
//наши любимые кресты отстрелят тебе ногу

Рецепт - использовать мозги. И умные указатели.

pon4ik ★★★★★
()
Ответ на: комментарий от MKuznetsov

Если присмотреться, то в коде отсутствуют volatile или статические переменные, каждый отдельный поток работает по сути с инвариантом. Так что гонки условий там отсутствуют и многопоточность ни при чём. Потоки просто быстрее приводят к фатальному UB, который там уже есть.

Dendy ★★★★★
()

Dendy, pon4ik, да, вы были правы, обращение к step2 было после его удаления. Мифическое падение даже после убирания step3 связано с тем, что убирал я его из не той тестовой функции :) Вот такое вот рукожопие.

А «подобный» код, работающий уже полгода, оказался не прям таким же, там была отдельная функция, проходящая весь граф и помечающая уникальные объекты к удалению, и вторая функция — для удаления отмеченных объектов.

staseg ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.