Comments 38
Спасибо за популяризацию данной темы и детальное описание "кишков"! А то, увы, на поверку оказывается, что большинство "разработчиков на куте" совершенно не умеют в потоки и высшим пилотажем оказывается наследование от QThread с переопределением run, а то, так и более убойные перлы типа obj->moveToThread(obj) и вариации этого...
Писал, писал чего-то — много воды, примеров — мало, без примеров не так понятно. И главное таки не написал, как правильно работать с потоками. А именно, код исполняемого объекта должен создаваться в функции void QThread::run()
. Если его создать в родительском потоке или даже в самом QThread
, например:
class MyThread: public QThread
{
MyObjects obj;
protected:
void run() override
{
obj.execute();
}
}
То данные и стек этого объекта будут всё равно находиться в родительском потоке, т.е. обращение всё равно будет к данным из другого потока, что чревато крэшами. Правильное объявление:
class MyThread: public QThread
{
protected:
void run() override
{
MyObjects obj;
obj.execute();
}
}
Вот привёл бы парочку подобных примеров — уже было бы гораздо понятнее! ;)
То данные и стек этого объекта будут всё равно находиться в родительском потоке
У объекта нет стека (стек — часть потока) и в приведенном примере если .execute() вызвано в потоке MyThread, то и стек будет находиться так же. Данные объекта в свою очередь не привязаны ни к одному потоку ибо лежат в «куче» (heap, говорящее название, не правда ли?). per se, чисто из того что написано в обоих примерах проблем нет.
Приведенный пример может крэшиться по другой причине — если MyObjects обрабатывают какие-то сигналы с данными которые меняет запущенный в другом потоке .execute. Но в этом случае второй пример (как показано) работать не будет, т.к. MyObjects просто не будут обрабатывать сигналы.
Как правильно:
MyObjects obj;
connect(this, startRunning, obj, execute)
QThread thread;
thread.run();
obj.moveToThread(&thread);
emit startRunning();
Это в предположении что MyObjects занимаются обработкой сигналов. Если нет и речь идет просто о запуске функции execute, то правильно
MyObjects obj;
QtConcurrent::run([&obj](){ obj.execute(); }); // assuming obj won't be destroyed soon
Вообще, как правило если Вы переопределяете QThread::run(), то Вы скорее всего делаете что-то неправильно.
Что-то мне кажется, что ваш "правильный" код будет вести себя крайне странно если выполнить его хотя бы два раза. Почему бы не сделать connect(thread, started, obj, execute)
?
А так-то да, started вполне можно использовать в качестве startRunning. Если совсем канонически то остановка потоков и уничтожение объектов (оставленные в моем примере полностью за скобками) тоже вешается аналогичным образом на сигнал finished
QThread* thread = new QThread;
MyObjects * worker = new MyObjects ();
worker->moveToThread(thread);
connect(thread, started, worker, execute);
connect(worker, finished, thread, quit);
connect(worker, finished, worker, deleteLater);
connect(thread, finished, thread, deleteLater);
thread->start();
Здесь предполагается что execute() должен обязательно сделать emit finished().
Работать должен без проблем, или я Вашу идею не вполне понял.
У вас в процессе работы накапливаются соединения с событием startRunning. Второе выполнение кода приведёт к вызову execute у двух объектов сразу, третье вызовет вызов execute у трёх объектов.
Или я что-то путаю, и connect срабатывает только 1 раз? В таком случае я не вполне понимаю как им пользоваться в GUI.
— мы не пользуемся в данном thread signal/slot
— мы сделали какой-то свой механизм остановки
— мы сделали какой-то (свой) механизм синхронизации, чтобы этот run() не молотил бы непрерывно
Вроде бы — в таком случае все ок?
Я почему-то последнее время все больше склоняюсь к тому, чтобы писать как можно больше std:: и как можно меньше Qt кода (только то, что иначе нельзя), даже в Qt приложении.
Есть ли какая нибудь особая причина использовать в Qt приложении именно QThread, а не std::thread?
Обработка очереди сообщений же.
У вас это чисто идеологическая неприязнь или все-таки имеет под собой какую-то практическую основу? Например в случае тех же потоков метасистема Qt хороша уже хотя бы тем, что при правильном использовании практически полностью снимает вопрос безопасного взаимодействия между потоками, ибо сигналы/слоты — потокобезопасный инструмент by design.
У меня тоже проблема с использованием stl и qt типов. Все никак не могу провести границу между тем, где использовать qt, а где stl. Точнее, границу то провожу, но на этой границе получаются постоянные конвертирования туда-сюда. И что с этим делать, не понятно
Если вам не нужна обработка сообщений из коробки (а то может быть и вообще не нужна, и таких ситуаций миллион), то конечно причин нет.
Уже даже и не спрашиваю про std::gui или std::sql.
Да, знаю, и в Qt шаг влево, шаг вправо, и прыжок на месте часто караются assertion или вообще acceess violation, а куда уйти то???
Но по поводу кроссплатформного GUI тут соглашусь, особо выбора нет.
юноша, я отлично программировал на Окнах. когда еще даже Borland не было
> Но по поводу кроссплатформного GUI тут соглашусь, особо выбора нет.
А что по поводу QtSql?
юноша, я отлично программировал на Окнах. когда еще даже Borland не было
Преклоняюсь: Windows 3.0 вышла в 1990, Borland C++ — в 1991. Правда, сама фирма Borland основана в 1981.., но это так…
Целый год отлично программировать на Win16 — это супер!
И, по крайней мере, до 2014 Turbo Pascal + Turbo Vision были основными инструментами для обучения программированию первокурсников ВМК МГУ. У меня тогда не выдержало сердце смотреть на мучения сына в 640х480 окне, и я прикрутил ему TP к Notepad++, благо TP запускается с командной строки, в DOSBox.
> иконок для создания приложений в Win. Другое дело, что этим не
> пользовался никто, во всяком случае, я об этом не слышал.
Wow, вот уж не ожидал тут встретить кого-то, кто помнит Borland Pascal!
Однако, тогда идая OWC, или как оно тогда назывась, была не готова, не то что MFC годами чуть позже.
> И, по крайней мере, до 2014 Turbo Pascal + Turbo Vision были основными
> инструментами для обучения программированию первокурсников ВМК МГУ.
Что это за помoйка? Я уже в конце 90-ых преподавал Delphi
так-как построение дерева объектов происходит в конструкторе исполняемом в главном потоке, то не привязанные к родителю объекты остаются в главном потоке.
такое случается когда забывают вставить ссылку на родителя в конструкторе или задействуют иные механизмы очистки. для однопоточного варианта не критично, а при переносе возникают проблемы.
и в qt-библиотеке случаются/случались/ конструкции не привязанные к родителю т.е. не переносимость в поток без доп ухищрений.
{
QThread *thread = QThread(this);
QObjectExample* obj = new QObjectExample(nullptr);
obj.moveToThread(thread)
}
QObjectExample::QObjectExample(QObject*)
{
// где-то глубоко в исходниках:
// кто-то писал для однопоточного кода
obj = new AnyQObject(/*или забыто this*/); // будет принадлежать главному потоку
}
А что сейчас стоит почитать в качестве учебника по современной Qt? Если последнее что как то изучалось — Qt4
Правильная работа с потоками в Qt