Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: xintrea от Ноябрь 30, 2017, 13:05



Название: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 13:05
Есть класс окна, в полях которого имеются два свойства:

Код:
 
protected:
    QThread moveDetectorThread;
    MoveDetector moveDetector; // Некий объект для запуска в потоке

В конструкторе есть следующий код, и он нормально запускает объект в потоке:

Код:
    // Запуск цикла объекта при старте потока
    connect(&moveDetectorThread, &QThread::started, &moveDetector, &MoveDetector::run);
    
    // Соединения для корректного завершения потока
    connect(&moveDetector, SIGNAL(finished()), &moveDetectorThread, SLOT(quit()));
    connect(&moveDetector, SIGNAL(finished()), &moveDetector, SLOT(deleteLater()));
    connect(&moveDetectorThread, SIGNAL(finished()), &moveDetectorThread, SLOT(deleteLater()));
    
    moveDetector.moveToThread(&moveDetectorThread); //  Объект переносится в тред
    moveDetectorThread.start(); // Тред запускается

Сам объект имеет метод основного цикла и метод, устанавливающий флаг выхода:

Код:
void MoveDetector::run()
{
    exitFlag=false;

    for(;;){
        update();
        if(exitFlag) {
            emit finished();
            return;
         }
    }
}


void MoveDetector::doExit()
{
    exitFlag=true;
}

В деструкторе окна я написал такой код:

Код:
    moveDetector.doExit();
    while(!moveDetectorThread.isFinished()) {
        qDebug() << "Wait finished move detector...";
    }

В результате, при срабатывании деструктора программа крешится вот так:

Код:
Wait finished move detector...
                 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Wait finished move detector...
Wait finished move detector...
Wait finished move detector...
The program has unexpectedly finished.
Процесс был завершён принудительно.
/home/xi/work/develop/cpp/MoveNoid/build-MoveNoid-Desktop_Qt_5_9_2_GCC_64bit-Debug/MoveNoid crashed.

Информацию брал вот отсюда: https://habrahabr.ru/post/150274/

Вопрос: почему крешится программа? Что сделать чтоб поток нормально завершался?

UPD: Сделал минимальный пример: http://rgho.st/8dD7n4ljc
При нажатии Stop все зависает, окно перестает отвечать.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Ноябрь 30, 2017, 13:43
Вот это уберите:

Код
C++ (Qt)
   connect(&moveDetector, SIGNAL(finished()), &moveDetector, SLOT(deleteLater()));
   connect(&moveDetectorThread, SIGNAL(finished()), &moveDetectorThread, SLOT(deleteLater()));
 

Эти объекты удалятся автоматически.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 13:56
Убрал, проблема осталась.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Ноябрь 30, 2017, 14:06
Убрал, проблема осталась.
Давайте попробуем так: :)
Код
C++ (Qt)
   moveDetector.doExit();
//    while(!moveDetectorThread.isFinished()) {
//        qDebug() << "Wait finished move detector...";
//   }
   moveDetectorThread.wait();
 


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Ноябрь 30, 2017, 14:07
Очередность коннектов и moveToThread никак не влияет?


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Ноябрь 30, 2017, 14:08
Кстати, это тоже не нужно:
Код
C++ (Qt)
connect(&moveDetector, SIGNAL(finished()), &moveDetectorThread, SLOT(quit()));
 


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Ноябрь 30, 2017, 14:18
Кто родитель у moveDetector?


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 14:22
Кстати, это тоже не нужно:
Код
C++ (Qt)
connect(&moveDetector, SIGNAL(finished()), &moveDetectorThread, SLOT(quit()));
 

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


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 14:23
Кто родитель у moveDetector?

QObject


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 14:34
Давайте попробуем так: :)
Код
C++ (Qt)
   moveDetector.doExit();
//    while(!moveDetectorThread.isFinished()) {
//        qDebug() << "Wait finished move detector...";
//   }
   moveDetectorThread.wait();
 

Сделал:

Код:
    moveDetector.doExit();
    moveDetectorThread.wait();
    qDebug() << "Success finish.";

Проблема практически все та же - поток останавливается, но окно перестает отвечать. И сообщения о завершении нет.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Ноябрь 30, 2017, 14:38
Код
C++ (Qt)
   moveDetector.doExit();
   moveDetectorThread.quit();
   moveDetectorThread.wait();
 

doExit завершает выполнения метода run MoveDetector'а, а после этого запускается цикл обработки событий самой нитки (в QThread::run вызывается метод exec()).
Вот второй quit будет завершать его.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Ноябрь 30, 2017, 14:47
В примере имеется вызов слота quit.
Но до него не доходит очередь из-за цикла проверки завершения нити. processEvents доказывает это.
В таком исполнении выход происходит.
Код
C++ (Qt)
   while(!moveDetectorThread.isFinished()) {
       qApp->processEvents();
       qDebug() << "Wait finished move detector...";
   }
 
Код выше написал только для объяснения. К использованию не рекомендую.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 14:49
doExit завершает выполнения метода run MoveDetector'а, а после этого запускается цикл обработки событий самой нитки (в QThread::run вызывается метод exec()).
Вот второй quit будет завершать его.

Не понял. То есть, у меня из-за того, что MoveDetector::run() имеет бесконечный цикл, то обработки событий нитки вообще не происходило?

Ваше исправление:

moveDetector.doExit();
moveDetectorThread.quit();
moveDetectorThread.wait();

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


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Ноябрь 30, 2017, 14:52
работает, но только один раз.

Коннекты и перемещение в нить нужно в конструктор вынести


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Ноябрь 30, 2017, 15:02
обработки событий нитки вообще не происходило?
Да.

Еще один вариант, добавить такой коннект:
Код
C++ (Qt)
connect(&moveDetector, &MoveDetector::finished, &moveDetectorThread, &QThread::quit, Qt::DirectConnection);
 
что-бы слот QThread::quit выполнялся прямым вызовом, а не ставился в очередь.
Тогда руками quit вызывать не придется.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Ноябрь 30, 2017, 15:07
Old, этот коннект, вроде, между нитками получается. Вроде ж нельзя так соединять.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Ноябрь 30, 2017, 15:11
Old, этот коннект, вроде, между нитками получается. Вроде ж нельзя так соединять.
Почему нельзя? :)
Если нам нужно, что бы метод вызвался сразу в момент посылки сигнала.

А так, он ставиться в очередь сообщений, которая не обрабатывается при ожидании в QThread::wait, и мы получаем цейтнот. :)


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: ssoft от Ноябрь 30, 2017, 15:12
Рабочий пример.

Коннекты нужно перенести в конструктор, чтобы не происходили по нескольку раз. А то поток не может завершиться по одному нажатию Finish.
В деструкторе quit, wait - об этом уже писали.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 15:17
Коннекты и перемещение в нить нужно в конструктор вынести

В конструтор чего? В конструктор MoveDetector или MainWindow?

Чем плохи коннекторы и перемещение, вызываемые при нажатии кнопки?


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Ноябрь 30, 2017, 15:22
Почему нельзя? :)
Видать, отложилось в подкорке
Цитировать
The connection type can be specified by passing an additional argument to connect(). Be aware that using direct connections when the sender and receiver live in different threads is unsafe if an event loop is running in the receiver's thread, for the same reason that calling any function on an object living in another thread is unsafe.
Только не соображу, как в данном случае работают eventloop и как обрабатывается соединение.

xintrea, у тебя просто по второму клику на кнопку старт повторно происходят коннекты и движение в нитку, где-то тут кроется проблема :)


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: xintrea от Ноябрь 30, 2017, 15:38
Рабочий пример.

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

А я думал, что при остановке треда происходит завершение треда...

Вот сейчас смотрю, и вижу, что метода, обратного moveToThread просто нет. То есть, по сути, если объект класса QThread у нас на стеке, то проинитить мы его можем один только раз... Хотя нет, мы же можем засунуть в этот тред другой объект... А почему тот же самый не можем заново засунуть?


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Ноябрь 30, 2017, 15:46
Только не соображу, как в данном случае работают eventloop и как обрабатывается соединение.
Ну давайте смотреть:
В конструкторе окна создаются два объекта классов QThread и MoveDetector. Оба этих объекта пока принадлежат главной нитке (она же GUI-нить), в которой они создавались.
Дальше происходит перемещении объекта moveDetector в контекст другой нити. Заметьте, что объект класса QThread, так и остался в главной нитке.
Теперь при отсылке сигналом из объекта moveDetector объекту нитки moveDetectorThread будет использован метод QueuedConnection, т.е. вместо прямого вызова слота, будет добавляться сообщение в очередь сообщений главной нити.
Вся цепочка выглядит так:
Мы в главной нитке вызываем doExit и вызываем wait, которая блокирует главную нить в ожидании завершения процесса (ее очередь сообщений при этом блокируется).
Теперь при переключении на рабочую нить, происходит отсылка сигнала finished, который добавляет сообщение в очередь главной нитки с вызовом метода QThread::quit. Но обработка сообщений в главной нитке - заблокировано, мы там ждем завершение нитки в wait.
Все, мы в цейтноте. В очереди главной нитки лежит сообщение для завершения рабочей нитке, но мы их не обрабатываем, потому что ждем ее завершение. :)


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Декабрь 01, 2017, 09:18
Как происходит блокировка, я понял.
Мне не совсем понятна цитата, приведённая в #19. Я её скопировал из документации к 4 qt. В qt5 уже такого не нашёл.
Цитировать
if an event loop is running in the receiver's thread
Что это значит? Что нельзя дёргать слот вне главной нитке или то что нельзя напрямую коннектить, т.к. в нитке имеется свой eventloop? Или вообще всё это не имеет значения относительно qt5?


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Old от Декабрь 01, 2017, 09:47
Что это значит? Что нельзя дёргать слот вне главной нитке или то что нельзя напрямую коннектить, т.к. в нитке имеется свой eventloop? Или вообще всё это не имеет значения относительно qt5?
Это зависит от слота. :)
QThread::exit под защитой мьютекса останавливает все запущенные в этой нитке eventloop. Этот слот безопасно можно вызывать из любой нитки.
А можно написать такой метод, который будет опасно вызывать из другой нитки. Например, если он будет не реентерабельным.

Простой пример, метод кеширует некоторые данные во время своего выполнения:
Код
C++ (Qt)
void Worker::run()
{
   m_cacheData = 0;    // Вначале сбросили кеш.
   while( !isEnd() )
   {
       // Долгие вычисления
       // m_cacheData читается и модифицируется
   }
}
 

Если мы запустим этот метод в контексте рабочей нитки, он начнет выполняться, прервется планировщиком в середине работы и управление получит другой поток, который напрямую вызовет этот метод (DirectConnection это прямой вызов), то кеши обновятся и когда управление вернется рабочей нитке, в кешах будет каша. Такие методы можно вызывать только через очередь.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: __Heaven__ от Декабрь 01, 2017, 11:30
QThread::exit под защитой мьютекса
Об этом можно было как-то узнать, не залезая в исходники?
Спасибо. Кажется, я всё понял.


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Igors от Декабрь 02, 2017, 09:41
Добавьте в on_buttonStart_clicked строку
Код
C++ (Qt)
moveDetectorThread.moveToThread(&moveDetectorThread);
 
Т.е. не только moveDetector, но и moveDetectorThread должны принимать ивенты в рабочей нитке. Иначе quit засылается в луп главной нитки, а она занята печатью. Ну и практичнее использовать moveDetectorThread.wait вместо цикла while 


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: zhbr от Декабрь 02, 2017, 10:00
Добавьте в on_buttonStart_clicked строку
Код
C++ (Qt)
moveDetectorThread.moveToThread(&moveDetectorThread);
 
Т.е. не только moveDetector, но и moveDetectorThread должны принимать ивенты в рабочей нитке. Иначе quit засылается в луп главной нитки, а она занята печатью. Ну и практичнее использовать moveDetectorThread.wait вместо цикла while 
Не надо moveDetectorThread помещать в тот же поток. "QThread objects are not threads; they're control objects around a thread, therefore meant to be used from another thread (usually, the one they're living in)" (c) https://wiki.qt.io/Threads_Events_QObjects


Название: Re: Сегфолт при завершении работы потока QThread - что не так?
Отправлено: Igors от Декабрь 02, 2017, 11:30
Не надо moveDetectorThread помещать в тот же поток. "QThread objects are not threads; they're control objects around a thread, therefore meant to be used from another thread (usually, the one they're living in)" (c) https://wiki.qt.io/Threads_Events_QObjects
Ага, это жевалось не раз. moveToThread(this) - это плохо! А почему? Да просто "так пишут", ну наверное правда :) Заметим что технически это совершенно корректно, речь идет только о том что это может быть не лучшим архитектурным решением.

Проблема (мелкая) в том что если moveDetector::finished испускается из рабочей нитки, то главная (в которой принимает события moveDetectorThread) не может принять его немедленно после установки флага, нужно выйти в событийный цикл. Это можно решать по-всякому. Возможно лучше всего убрать moveDetector::finished, т.е. просто "выйти вон" по exitFlag, а quit/exit сделать из главной нитки как в варианте ssoft. Если проблема ясна, то решения найдутся