LINUX.ORG.RU

Как остановить поток Qt с очисткой ресурсов

 ,


0

1

Добрый день. Создаю поток, наследуясь от QThread (на хабре статью читал, что так не надо). В потоке в методе run создают динамический объект. Как мне можно остановить этот поток, при этом что бы объект был удален и вызван метод объекта для закрытия ресурсов. Смотрел варианты когда в run крутится вечный цикл

while( !flStop ) {
    //do work
}
но у меня все построено на событиях
MyThread::run() {
    mSerial = new QSerialPort;
    mSerial->open(); //вопрос что если не откроем, пока оставим
    exec();
}
, приходят сигналы и вызываются соответствующие им методы объекта, т.е. если я попадаю в метод потока где удаляю ресурсы
MyThread::clear() {
    mSerial->close();
    delete mSerial;
}
то мне надо и тормозить тут же поток, т.к. может прийти вызов другого метода
MyThread::write(QByteArray someBytes) {
    mSerial->write(someBytes);
}
а mSerial уже удален, т.е. если я все правильно понимаю, метод clear должен быть финальным и в нем необходимо себя останавливать, либо эмитить сигнал и заставлять другой объект останавливать MyThread, но если я заэмичу сигнал, то где гарантии, что мне не прилетит сигнал на запись в mSerial. Т.е. получается при таком подходе я должен в clear и тормозить сам себя методом quit(). Такой вот вопрос



Последнее исправление: da17 (всего исправлений: 2)

эмитить сигнал и заставлять другой объект останавливать MyThread

[br]MyThread::clear() {[br]    mSerial->close();[br]    delete mSerial;[br]    mSerial = nullptr;[br]    emit stopMe();[br]}[br]



[br]MyThread::write(QByteArray someBytes) {[br]    if(mSerial)[br]        mSerial->write(someBytes);[br]}[br]

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

Ага, я понял, спасибо. Изящно. Но вот именно в моем случае, это будет ок, если я прям в clear тормозну себя.

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

А что будет, если mSerial таки удалится сразу после того как условие выполнится, но до записи, или сразу после разыменования?

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

Сорри за «br», интернет забаговал именно в момент редактирования. User line break с тегом коде ведёт себя просто идеально, да LOR?

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

Так наверное такие же знатоки в юзабилити делали, как ты в многопоточном программировании :) И да, превью никто не отменял.

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

Так и clear и write ведь обработчики событий, а значит выполнятся в одном и том же потоке — в потоке MyThread. А значит одно не начнется, пока другое не закончилось.

Всегда ваш К.О.

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

Ну да, пока кто-то не приконнектится к этому потоку не функой по умолчанию, хм. На каком нить уровне иеррархии, где даже не известно, что это поток. Нет уж, не должна инкапсулированая сущность зависеть от метода её вызова.

pon4ik ★★★★★
()

mSerial = new QSerialPort(this) - и удалять самому не надо

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

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

Этого не может быть, потому что быть не может. А ещё потому, что в реализации QThread таки написано в начале Q_OBJECT.

Но даже если бы Q_OBJECT там не было, известно что это наследник QObject (иначе ты попросту не приконнектишь). А дальше Qt обеспечит кьюинг обработчиков. А если коннектишь через std::bind или лямды, то ССЗБ.

Но даже если бы Qt не обеспечивал, то ТС нигде не говорил, что это вот все находится в публичном интерфейсе либы, а значит все места, где коннектится подконтрольны.

Нет уж, не должна инкапсулированая сущность зависеть от метода её вызова.

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

Но ты можешь на каждый чих лочить мутекс, ждать пока рак на горе свистнет и т.д. И будешь таким же спецом в параллельном программировании, каким был бы спецом в управлении памятью гипотетический чувак, завернувший абсолютно всё в std::shared_ptr, вместо стека, виков и уников. Безопасно, но не оптимально.

robus ★★★★★
()

В общем подытожив - exit() в очистке нормальный вариант(не quit). Но всё равно, без блокировки не защитит от кривых рук клиента.

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

Если не веришь мне и докам, можешь глянуть сорцы QEventLoop, и в частности то, как выбирается текущий цикл обработки событий.

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

И алыверды, для тех кто боится мьютексов :) Можно посмотреть, что происходит при Auto соединении. Там как раз всегда проверяется какому потоку принадлежит объект(раз лок), если не текущему лочится обьект треда(два лок), если в нём запущен цикл обработки событий, то лочится его очередь(три лок) иначе вроде лочится главный цикл и сообщение попадает туда(точно не помню уже). А вот если в ручную тип соединения указать, можно немного сэкономить на спичках.

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)

Кстати про Auto соединение, мне лень читать, но скорее всего, нужно ещё обьекту твоего потока сделать moveToThread, что бы всё работало, как ты задумываешь. Т.к. создаётся сам инстанс QThread в основном потоке. А вот мувит ли он сам себя - х3, я бы не стал например.

pon4ik ★★★★★
()

на хабре статью читал, что так не надо

А что это за статья?

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

Можно посмотреть, что происходит при Auto соединении. Там как раз всегда проверяется какому потоку принадлежит объект(раз лок), если не текущему лочится обьект треда(два лок), если в нём запущен цикл обработки событий, то лочится его очередь(три лок) иначе вроде лочится главный цикл и сообщение попадает туда(точно не помню уже)

Это те расходы, которые ТС собрался нести.

но у меня все построено на событиях

Чтобы избавиться от них ему придётся переписать всё на QtConcurrent, или, на худой конец, переписать MyThread на бесконечный цикл. Если тебе интересно моё мнение, то да, это стоит сделать.

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

Поэтому, я указываю. Но уж никак не Qt::DirectConnection, где автоматика бы выставила Qt::QueuedConnection. Qt::DirectConnection ставлю только тогда, когда точно знаю, что обработчик нужно выполнить в том же потоке, где заэмитился сигнал.

P.S. не нужно так сильно хейтить реентрабельность и везде насаживать полную тредсафе.

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

нужно ещё обьекту твоего потока сделать moveToThread, что бы всё работало, как ты задумываешь

Да ладна, как жи так та?!

Т.к. создаётся сам инстанс QThread в основном потоке

Спасибо, Кэп.

А вот мувит ли он сам себя - х3

Нет не мувит. Это делает код ТС, коль скоро тут вопрос об остановке потока, а не «Помогите, не работает многопоточность в Qt5».

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

Это те расходы, которые ТС собрался нести.

Это те расходы, которые решает нести или нет продвинутый пользователь api. Внутреннее оно или внешнее особой роли не играет с ростом проекта. А экономия на локе в современных(последние лет 10) системах - нужна только когда ты начала считать сотни наносекунд. Что в gui приложения огромная редкость (лично я ни разу не видел). Если драки на блокировке не происходит, то она стоит как пара разыменовываний указателей, особенно если мимо кэша.

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

Если тебе интересно моё мнение, то да, это стоит сделать.

Проделай, лучше когда нибудь упражнение - свой бесконечный цикл и QEventLoop и глянь у кого окажется трупут выше и лэйтенси ниже. Я бы со всеми накладными расходами поставил таки на кутишников, т.к. эта горячая точка годами оптимизируется не глупыми товарищами :)

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

А экономия на локе в современных(последние лет 10) системах - нужна только когда ты начала считать сотни наносекунд. Что в gui приложения огромная редкость (лично я ни разу не видел).

Ну давай, забей на сотни наносекунд в paintEvent-е своей кастомной, красивенькой в цветах твоего проекта/компании, кнопочки. Которых на всех формах всех окон сотни. Вангую, что с таким подходом ты вскоре придёшь к стойкому убеждению, что QPainter тормозит, и вообще с графикой в линухе полная габелла, нативная кроссплатформа мертва, Qt разжирел и не торт больше вовсе.

robus ★★★★★
()

метод объекта для закрытия ресурсов

Деструктор?

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

Считал я эти микросекунды. Одно дело когда какой нить софт риалтайм с 10г сетевухами, а другое дело когда ui поток без логики с самым распрекрасным opengl на борту спит 50% времени даже если пользователь бьётся в эпилептических припадках в обнимку с клавой и мышью.

Если что, моник моргает с частотами порядка миллисекунды.

pon4ik ★★★★★
()

Т.е. получается при таком подходе я должен в clear и тормозить сам себя методом quit()

И в чем подвох?

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

Сигналы сосут

23:10:16: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 83,36900 ms
Velosiped (TM): 6,79000 ms
Qt Concurrent: 4,81400 ms
23:10:16: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:21: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 83,96800 ms
Velosiped (TM): 6,02300 ms
Qt Concurrent: 4,02200 ms
23:10:21: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:21: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 93,91400 ms
Velosiped (TM): 6,81400 ms
Qt Concurrent: 3,79700 ms
23:10:22: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:22: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 98,48000 ms
Velosiped (TM): 7,08200 ms
Qt Concurrent: 4,88500 ms
23:10:22: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:23: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 86,67700 ms
Velosiped (TM): 6,75400 ms
Qt Concurrent: 3,71200 ms
23:10:23: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:23: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 100,09700 ms
Velosiped (TM): 7,41600 ms
Qt Concurrent: 3,82200 ms
23:10:23: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:24: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 95,05300 ms
Velosiped (TM): 6,74600 ms
Qt Concurrent: 5,24600 ms
23:10:24: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:25: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 93,84800 ms
Velosiped (TM): 6,14900 ms
Qt Concurrent: 3,94100 ms
23:10:25: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:25: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 90,41500 ms
Velosiped (TM): 6,99000 ms
Qt Concurrent: 5,09000 ms
23:10:25: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:26: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 94,83900 ms
Velosiped (TM): 6,74600 ms
Qt Concurrent: 5,30200 ms
23:10:26: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:26: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 87,92100 ms
Velosiped (TM): 5,99300 ms
Qt Concurrent: 5,18300 ms
23:10:27: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:27: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 89,82200 ms
Velosiped (TM): 5,82700 ms
Qt Concurrent: 4,72100 ms
23:10:27: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0

23:10:28: Starting /home/robus/src/newApp/build-threads_benchmark/threads_benchmark...
Qt QObject worker: 88,11900 ms
Velosiped (TM): 6,96300 ms
Qt Concurrent: 5,10000 ms
23:10:28: /home/robus/src/newApp/build-threads_benchmark/threads_benchmark exited with code 0



https://gitlab.com/VadikLeshy/threads_benchmark

robus ★★★★★
()
Последнее исправление: robus (всего исправлений: 1)
Ответ на: комментарий от annulen

Подвох в том, что непонятно когда можно удалять поток. Вот есть где-то объект которые владеет указателем на поток. Я так понимаю, что когда мы удалили ресурсы, остановили поток, можно эмитить сигнал, что теперь объект потока можно и удалить.

//где-то ранее

connect( mCommander, SIGNAL(stop_thread(), mMyThread, SLOT(clear()));

connect(mMyThread, SIGNAL(thread_stopped, mCommander, SLOT(stop_confirmed() ));
Тут после закрытия ресурсов и остановки потока эмитим, что мы все почистили и остановили поток, можно нас удалить.

MyThread::clear() {
    mSerial->close();
    delete mSerial;
    quit();
    emit thread_stopped();
}


void Commander::stopComThread() {
    emit stop_thread(); //приходим в clear mMyThread
}
void Commander::stop_confirmed() {
    //Тут уж можно и удалять 
    delete mMyThread;   
}

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

Подвох в том, что непонятно когда можно удалять поток.

deleteLater() себе, если это не может сделать вызывающая сторона

annulen ★★★★★
()
Ответ на: Сигналы сосут от robus

Ну, раз сосут ничего тут не поделаешь. Правда не очень мне понятно почему в коде, который соревнуется с циклом обработки сообщений не используется ни семафоров ни условных переменных, но наверное так можно.

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

Смотря в каком потоке вызывается MyThread::clear() - я предполагал, что она выполняется в самом потоке, тогда quit() приводит к выходу из QEventLoop и немедленному завершению главной функции потока

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

Именно, да в самом потоке. Если будет выход из QEventLoop, я смогу отослать сигнал (свой) или все уже.

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

Если будет выход из QEventLoop, я смогу отослать сигнал

Queued connection обрабатывается в QEventLoop получателя, а не отправителя

annulen ★★★★★
()
class ThreadObject : public QObject
{
    Q_OBJECT
public:
    ThreadObject();
    ~ThreadObject();
    void shortTask();
    void longTask();
private slots:
    void shortTask_inthread();
    void longTask_inthread();
public slots:
    void onSignalReaction();
private:
    QThread t;
    Q_INVOKABLE void init_inthread();
    Q_INVOKABLE void close_inthread();
    QPointer<Some> obj;
};

ThreadObject::ThreadObject()
{
    moveToThread(&t);
    t.start();
    QMetaObject::invokeMethod(this,"init_inthread",Qt::BlockingQueuedConnection);
}

ThreadObject::~ThreadObject()
{
    QMetaObject::invokeMethod(this,"close_inthread",Qt::BlockingQueuedConnection);
    t.quit();
    t.wait();
}

ThreadObject::shortTask()
{
    QMetaObject::invokeMethod(this,"shortTask_inthread",Qt::QueuedConnection);
}

ThreadObject::longTask()
{
    QMetaObject::invokeMethod(this,"longTask_inthread",Qt::QueuedConnection);
}

ThreadObject::shortTask_inthread()
{
    if(obj.isNull()) return;
    obj->doShortTask();
}

ThreadObject::longTask_inthread()
{
    if(obj.isNull()) return;
    if(obj->longTaskIsDone()) return;
    obj->doLongTaskIteration();
    QMetaObject::invokeMethod(this,"longTask_inthread",Qt::QueuedConnection);
}

ThreadObject::onSignalReaction()
{
    if(obj.isNull()) return;
    obj->doReaction();
}

void ThreadObject::init_inthread()
{
    obj = new Some();
}

void ThreadObject::close_inthread()
{
    delete obj;
}
anonymous
()
Ответ на: комментарий от anonymous

Я всю жизнь наследовался и дед мой от QThread наследовался. С какого хрена я там буду объектно-ориентированной парадигме прогибаться? Как сподручней в данном случае так и сделаю. Вообще мне нужны очень веские аргументы что бы не наследоваться (от хорошего к лучшему). Что касается рекомендуемого метода, мне его сложней осознавать и как следствие я выбрал что проще.

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

Ну и статья старовата 2012. Если уж так плохо наследоваться от QThread почему бы вообще не запретить от него наследоваться?

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

Вообще мне нужны очень веские аргументы что бы не наследоваться

slots in a QThread derived class are usually a code smell, because they'll run in the thread where the QThread QObject lives and not in the thread itself.
https://github.com/KDE/clazy/blob/master/docs/checks/README-thread-with-slots.md

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