In a previous article we are only a little touch to working with threads , and in the version that is more for the customization of thread, although it can be used to perform outside work not directly related to the thread. That is an option with inheritance from QThread and override the method run().
Now we will create ExampleObject class whose objects will be transferred to the individual threads via moveToThread() method and implemented in most of these flows. A useful work will make the slot method run() , which will be defined in this class. What is important, object class will inherit from QObject .
In order to work in the run() method can be performed in cycles, using a while loop, which will operate using variable bool m_running. For the convenience of working with this variable we define it as Q_PROPERTY . But at the end of the work will emit finished() signal.
Work algorithm
- Create an QThread object and the ExampleObject class object;
- Connect the signal QThread::started() method to ExampleObject::run() ;
- Connect the signal ExampleObject::finished() to slot QThread::terminate() , so that on completion of useful work to complete the flow;
- Set m_running variable to true, to allow the work cycle, or else this method once completed;
- Run stream using the method start() ;
- When it is necessary to complete useful work object, set the variable in m_running to false. Running run() method and the flux in which he lives the object will be completed automatically and correctly.
Project structure and appearance of the application
- ThreadLessonTwo.pro - the profile of the project;
- exampleobject.h - header of the object file to be transmitted in a thread;
- exampleobject.cpp - file source object that will be passed in the thread;
- mainwindow.h - header file of the main application window;
- mainwindow.cpp - file source code of the main application window;
- mainwindow.ui - file forms the main application window;
- main.cpp - the file source code to the main function.
Attached are two objects to be determined, and the two streams. With the help of the application interface, we will show some information in the output qDebug() , and will start and stop the thread.
exampleobject.h
To begin, consider the contents of the object header file. In this file, three properties announced Q_PROPERTY :
- running - it is a variable, by means of which will be implemented in the control cycle a run() method and, accordingly, will affect the completion of the useful work of the object.
- message - a string that will be sent to output in qDebug() from the main application window
- message_2 - this is the second line, which will also be displayed in qDebug() , but will also be used for transmission of the second stream.
#ifndef EXAMPLEOBJECT_H #define EXAMPLEOBJECT_H #include <QObject> class ExampleObject : public QObject { Q_OBJECT // A property that controls the work of thread Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged) // The first message in the object Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) // The second message that will be transmitted via a signal/slot to the second object Q_PROPERTY(QString message_2 READ message_2 WRITE setMessage_2 NOTIFY message_2Changed) bool m_running; QString m_message; QString m_message_2; int count; public: explicit ExampleObject(QObject *parent = 0); bool running() const; QString message() const; QString message_2() const; signals: void finished(); // The signal, in which we will complete the thread after the end of the run method void runningChanged(bool running); void messageChanged(QString message); void message_2Changed(QString message_2); void sendMessage(QString message); public slots: void run(); // Method with payload which can be carried out in a loop void setRunning(bool running); void setMessage(QString message); void setMessage_2(QString message_2); }; #endif // EXAMPLEOBJECT_H
exampleobject.cpp
All our interest is reduced to the method run() , in which the while loop will increment the counter count and the information will be displayed with the message m_message , m_message_2 and this counter as long as the variable m_running not be exposed to a value of false. At the exit of the loop at the end of execution run() method will be released finished() signal, in which the flow is completed, which will be the object.
#include "exampleobject.h" #include <QDebug> ExampleObject::ExampleObject(QObject *parent) : QObject(parent), m_message(""), m_message_2("") { } bool ExampleObject::running() const { return m_running; } QString ExampleObject::message() const { return m_message; } QString ExampleObject::message_2() const { return m_message_2; } // The most important method, which will be carried out "useful" work object void ExampleObject::run() { count = 0; // Variable m_running responsible for the object in the stream. // If false, the work is completed while (m_running) { count++; emit sendMessage(m_message); qDebug() << m_message << " " << m_message_2 << " " << count; } emit finished(); } void ExampleObject::setRunning(bool running) { if (m_running == running) return; m_running = running; emit runningChanged(running); } void ExampleObject::setMessage(QString message) { if (m_message == message) return; m_message = message; emit messageChanged(message); } void ExampleObject::setMessage_2(QString message_2) { if (m_message_2 == message_2) return; m_message_2 = message_2; emit message_2Changed(message_2); }
mainwindow.h
The header of the main window of the application declared slots for processing keystrokes to record messages to the objects that will be carried out in the streams, as well as slots for starting and stopping the flow. In addition, we declare two objects ExampleObject class and two objects of QThread class.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QThread> #include "exampleobject.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void on_write_1_clicked(); // Slot for writind data from lineEdit_1 to the first object in the first stream void on_write_2_clicked(); // Slot for writind data from lineEdit_1 to the second object in the first stream void on_start_clicked(); void on_stop_clicked(); private: Ui::MainWindow *ui; QThread thread_1; QThread thread_2; ExampleObject exampleObject_1; ExampleObject exampleObject_2; }; #endif // MAINWINDOW_H
mainwindow.cpp
And now combine all of the previous code in a working application in the source code of the main program window. In the constructor of the class, as has been said in the beginning, you need to connect signals started() of the threads to the slots run() our test objects, and signals the finished(), to the slots terminate() of threads to complete the work streams at completion method run().
Also joined sending message signal exampleObject_1 object to slot installation exampleObject_2 messages. But that information can be transmitted, you need the fifth argument in the connect method to transfer the flag Qt :: DirectConnection, which would establish a direct connection of objects and allows to transmit information through a system of signals and slots .
And for the transfer of objects in the threads necessary to use the method moveToTrhead().
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(&thread_1, &QThread::started, &exampleObject_1, &ExampleObject::run); connect(&thread_2, &QThread::started, &exampleObject_2, &ExampleObject::run); connect(&exampleObject_1, &ExampleObject::finished, &thread_1, &QThread::terminate); connect(&exampleObject_2, &ExampleObject::finished, &thread_2, &QThread::terminate); connect(&exampleObject_1, &ExampleObject::sendMessage, &exampleObject_2, &ExampleObject::setMessage_2, Qt::DirectConnection); exampleObject_1.moveToThread(&thread_1); exampleObject_2.moveToThread(&thread_2); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_write_1_clicked() { exampleObject_1.setMessage(ui->lineEdit_1->text()); } void MainWindow::on_write_2_clicked() { exampleObject_2.setMessage(ui->lineEdit_2->text()); } void MainWindow::on_start_clicked() { exampleObject_1.setRunning(true); exampleObject_2.setRunning(true); thread_1.start(); thread_2.start(); } void MainWindow::on_stop_clicked() { exampleObject_1.setRunning(false); exampleObject_2.setRunning(false); }
Coclusion
As a result, the application can be obtained following the following conclusion qDebug(), which shows the parallel operation of facilities in the streams, as well as the transfer of information from one thread to another.
"thread 1" "" 11422 "thread 2" "thread 1" 11446 "thread 1" "" 11423 "thread 2" "thread 1" 11447 "thread 1" "" 11424 "thread 2" "thread 1" 11448 "thread 1" "" 11425 "thread 2" "thread 1" 11449 "thread 1" "" 11426 "thread 2" "thread 1" 11450 "thread 1" "" 11427 "thread 2" "thread 1" 11451
Download the project can be on the following link: Thread Lesson Two
У меня происходит переполнение счетчика count, появляется ошибка malloc(): memory corruption (fast). Не подскажите, как с этим бороться?
А что делали? Повторяете урок или как? Пытались просто скачать проект в конце статьи и запустить?
Взял Ваш пример для Qt4.8 (не спрашивайте почему). переписал связь сигнал/слот след. образом:
"thread 1" "" 1752
"thread 2" "" 1747
"thread 1" "" 1753
"thread 2" "" 1748
"thread 1" "" 1754
т.е. связь между потоками не работает.
Вижу еще волшебное слово Qt::DirectConnection, пробовал добавить - нет результата
Добавил еще в pro файл запись CONFIG += no_keywords - тоже не помогло
Что подскажите
PS: я нубер в Qt и плюсах
Не вижу всего кода, по этому куску кода невозможно сказать, в чём ошибка. На первый взгляд код написан верно.
В остальном код как и Вашем примере
Тогда мне больше нечего Вам сказать. Давно уже не работал с Qt 4.8 и приниципиально не работаю с ним сейчас.
В qt5.6 всё нормально заработало. С 4.8 - нет
Нууу... тут уже вопрос к самому Qt4.8. Если честно, идей нет, да и копаться в deprecated коде желания тоже нет.
В моем коде с двумя потоками и выводом сообщений в textEdit при использовании Qt::DirectConnection падает в "The program has unexpectedly finished" через 10-20 сообщений. При использовании Qt::QueuedConnection, работает.
Qt::DirectConnection
— сигнал обрабатывается сразу вызовом соответствующего метода слотаQt::QueuedConnection
— сигнал преобразуется в событие и ставится в общую очередь для обработкиQt::AutoConnection
— это автоматический режим, который действует следующим образом: если отсылающий сигнал объект находится в одном потоке с принимающим его объектом, то устанавливается режимQt::DirectConnection
, в противном случае — режимQt::QueuedConnection
. Этот режим (Qt::AutoConnection
) определен в методеconnection()
по умолчанию.В принципе все варианты возможны, зависит от ситуации.
Наиболее правильным по максимому использовать Qt::AutoConnection. Если что-то по какой-то причине не работает, то разбираться в чём причина и возможно переходить на Qt::DirectConnection , при данном методе слот будет вызываться сразу же, юез постановки в очередь событий. Иногда может потребоваться, чтобы дать приоритет вызову слота. Ведь слоты вызываются в той последовательности, как они был подключены в коде.
Тут уже возникает вопрос в потокобезопасности. Поэтому сложный код следует блокировать мьютексами.Теми же самыми QMutexLocker и QMutex, например,
В том и дело что хотел без мютексов обойтись. т.е. суть проблемы есть старая программа, рабочая и три потока в ней A-B-(C1...Cn) .
Я глубоко в дебри многопоточности не зарывался, но если разбираться с нюансами и поднять в памяти то, что мне говорили более опытные коллеги. То можно сделать следующие выводы:
Возможно таким же способом передать туда QTcpSocket?
Да, это должно работать. Главное при передаче данных в основные потоки защищайте данные от конкурентного доступа мьютексами.
Не получается сделать connect, получаю гору ошибок. В чем может быть дело ?
connect(&thread01, &QThread::started, Server, &myserver::run);
Ввожу 1 в Write 1, ввожу 2 в Write 2, нажимаю Start и сразу Stop. Сначала вижу очень много одинаковых строк, а потом сообщение об ошибке. При этом программа не крашится, но предупреждение настораживает... Почему так? Неужели надо &QThread::terminate заменить на &QThread::quit ? Вот что у меня вышло после. Варнинг перестал сыпаться...
Ubuntu 22, Qt 6.
"1" "" 212463
"2" "1" 253535
"1" "" 212464
"1" "" 212465
"2" "1" 253536
"2" "1" 253537
Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt.
You must not let any exception whatsoever propagate through Qt code.
Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt.
You must not let any exception whatsoever propagate through Qt code.
Вообще, правильнее использоать quit, поскольку terminate - это более жёсткий вариант, на тот случай, если поток схватил deadlock.
Так что в итоге вы правильно всё исправили. Статья старая, на тот момент я делал больше глупых ошибок :-)
После того, как было сделано
Объект exampleObject_1 стал жить в своей ните, а не в той же, где живет MainWindow
Почему же в методе MainWindow::on_write_1_clicked()
Можно обращаться к методам exampleObject_1?
Разве могут взаимодействовать объекты из разных нитей как-то, кроме как через сигнал-слоты?
В продолжение моего предыдыщуего поста.
Похоже, нельзя обращаться к полям и методам объекта А из объекта Б, если они в разных нитях живут. Поэтому вместо
Следует писать
А вместо
Стоит писать
И на всякий случай выскажусь по поводу конструктора ExampleObject::ExampleObject(). Сейчас он пустой, но нужно проговорить. В этом конструкторе ни в коем случае нельзя выделять память из кучи (делать new), подробности тута см "NEVER allocate heap objects (using new)".
Там всё равно есть ссылочная информация на этот объект, просто... да... всё может посыпаться в более сложной логике. Пример очень старый, может быть неактуален или в чём-то неправилен в настоящее время.
"Запуск выполнения метода run будет осуществляться по сигналу запуска от соответствующего потока"
Мой душнила шепчет, что корректнее было бы написать
"Запуск выполнения метода run будет осуществляться по сигналу запуска от соответствующего объекта (thread_1 или thread_2) QThread."
Почему это важно? Может показаться, что существуют какие-то статические сигналы, которые способен emit непосредственно класс QThread. По аналогии со статическими методами...
Верно?
Ну да, так конечно можно и до столба докопаться, но с другой стороны точность не бывает лишней в некоторых вопросах :)
Вот тут можно посмотреть аналогичный современный проект (правда с одной дополнительной нитью).
Я поясню свой вопрос. Выше я писал
"Почему же в методе MainWindow::on_write_1_clicked()
Можно обращаться к методам exampleObject_1?
Разве могут взаимодействовать объекты из разных нитей как-то, кроме как через сигнал-слоты?"
Вот чего я боюс:
"Ещё совсем уж вскользь упомяну, что реализация SMP в процессорах Интел совершенно всепрощающа и гарантирует программисту, что все процессоры видят память одинаково, хотя и имеют раздельные кеши. То есть обеспечивают так называемую когерентность кешей процессоров в многопроцессорной системе.
Это не везде так. Есть процессоры, в которых надо предпринимать специальные усилия для того, чтобы данные гарантированно были синхронизированы между процессорами. Или явно выключать кеширование для страниц памяти, в которых лежат данные, доступные нескольким процессорам. Это уже тема для совершенно отдельного разговора, здесь я лишь упомянул для полноты."
Ответ Евгения "Там всё равно есть ссылочная информация на этот объект" как-то не очень успокаивает... Есть ссылка на область (какой-то) памяти, с чего бы вдруг одной и той же для разных-то нитей?..
Могут. Выполняя оператор new , Вы выделяете под объект память в куче (heap), и если вы передаёте указатель на этот объект в 10 потоков, то код во всех 10-ти потоках может работать с этим указателем и модифицировать объект. В этом случае важным является защищать процесс записи мьютексами, чтобы не возникло состояниее гонки и данные не накрылись медным тазом. А сигнал слотовое соединение - это механизм потокобезопасной передачи данных, но это абсолютно не означает, что через это соединение нельзя передать указатель на объект в другом потоке, и не означает, что не нужно записываемые данные защищать мьютексами.
А касательно той статьи на Хабре, то это системное программирование на микроконтроллерах с использованием чистого Си, судя по коду, может я и ошибаюсь, но меня берут сомнения, что там примеры на С++. Да и автор в комментариях упоминает о Atmega 128.
Поэтому и рассуждение на тему организации памяти в процессоре исходя из статьи для систем реального времени на микроконтроллерах хоть и применимо для десктоп приложений, для которых в первую очередь разрабатывался Qt, тем не менее всё же выглядит высасыванием проблемы из пальца. Хотя бы потому, что в таком случае нужно уже говорить относительно конкретной модели процессора, на котором происходит разработка. В противном случае разговор является беспредметным.