Форум программистов, компьютерный форум, киберфорум
C++ Qt
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.57/200: Рейтинг темы: голосов - 200, средняя оценка - 4.57
385 / 229 / 12
Регистрация: 06.07.2011
Сообщений: 512
1

Два способа использования QThread

19.05.2012, 23:26. Показов 39564. Ответов 13
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Тема скорее для обсуждения и для кого-то информационная.

Для примера возьмем традиционный пример - создать два класса (TextClass), выдающих свой номер в стандартный вывод. Вывод распараллелить по времени.

Класс, печатающий в поток:
TestClass.h
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef TESTCLASS_H
#define TESTCLASS_H
 
#include <QObject>
 
class TestClass : public QObject
{
    Q_OBJECT
    public:
        TestClass(int);
    private:
        int num;
    public slots:
        void run();
    signals:
        void finished();
};
 
#endif // TESTCLASS_H
TestClass.cpp
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
#include "TestClass.h"
#include <QDebug>
 
TestClass::TestClass(int i)
{
    num=i;
}
 
void TestClass::run() {
    for (int i=0; i< 5000; ++i) {qDebug()<<"from "<<num; }
    emit finished();
}
1 подход. Классический.
Стандартный метод наследования от QThread, переопределения метода run() и запуска. Описан в документации.

Создаем специализированные потоки:
SpecialThread.h
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef SPECIALTHREAD_H
#define SPECIALTHREAD_H
 
#include <QThread>
#include "TestClass.h"
 
class SpecialThread : public QThread
{
public:
    SpecialThread();
    void setTestClass(TestClass*);
private:
    TestClass* pTestObject;
public:
    void run();
};
#endif // SPECIALTHREAD_H
SpecialThread.cpp
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "specialthread.h"
 
SpecialThread::SpecialThread()
{
    pTestObject=0;
}
 
void SpecialThread::setTestClass(TestClass *pTO) {
    pTestObject=pTO;
}
 
void SpecialThread::run() {
    if (pTestObject) {
         pTestObject->run();
    }
}
Дальше даем потокам знать о печатающих объектах и запускаем:
main.cpp
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <QtCore/QCoreApplication>
#include "TestClass.h"
#include <QThread>
#include <QtConcurrentRun>
#include "SpecialThread.h"
 
int main(int argc, char *argv[]){
 
    QCoreApplication a(argc, argv);
 
//------- Way 1 ---------
 
    SpecialThread thread1;
    SpecialThread thread2;
 
    TestClass text1(1);
    TestClass text2(2);
 
    thread1.setTestClass(&text1);
    thread2.setTestClass(&text2);
 
    thread1.start();
    thread2.start();
 
    return a.exec();
}
2 подход. Как правильно.
Используем QThread "как задумывалось создателем". С версии 4.4 QThread не является абстрактным классом и его можно инстанцировать. Класс стал именно интерфейсом для доступа к управлению потоком, но не представлением самого потока, как это сделано в подходе 1.

Механизм строится целиком на сигналах-слотах. Сигнал запуска потока запускает и вывод на консоль. Сигнал окончания вывода останавливает поток. Код:
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <QtCore/QCoreApplication>
#include "TestClass.h"
#include <QThread>
#include <QtConcurrentRun>
#include "SpecialThread.h"
 
int main(int argc, char *argv[]){
 
    QCoreApplication a(argc, argv);
 
//------- Way 2 ---------
 
    QThread thread1;
    QThread thread2;
 
    TestClass text1(1);
    TestClass text2(2);
 
    QObject::connect(&thread1,SIGNAL(started()),&text1,SLOT(run()));
    QObject::connect(&text1,SIGNAL(finished()),&thread1,SLOT(terminate()));
 
    QObject::connect(&thread2,SIGNAL(started()),&text2,SLOT(run()));
    QObject::connect(&text2,SIGNAL(finished()),&thread2,SLOT(terminate()));
 
    text1.moveToThread(&thread1);
    text2.moveToThread(&thread2);
 
    thread1.start();
    thread2.start();
 
    return a.exec();
}
3 подход. Простейший.
Не относится к QThread, но стоит указать способ с QtConcurrent::run() как наиболее простой и "продвинутый":

C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <QtCore/QCoreApplication>
#include "TestClass.h"
#include <QThread>
#include <QtConcurrentRun>
#include "SpecialThread.h"
 
int main(int argc, char *argv[]){
 
    QCoreApplication a(argc, argv);
 
//------- Way 3 ---------
 
    TestClass text1(1);
    TestClass text2(2);
 
    QtConcurrent::run(&text1,&TestClass::run);
    QtConcurrent::run(&text2,&TestClass::run);
 
    return a.exec();
 
}
Собственно вот. Кто какие минусы и плюсы видит в 1-м и 2-м подходе?

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

2-ой подход избавляет от написания лишнего кода и все стройно укладывается в идеологию сигналов-слотов. Передать в новый поток можно любой объект и любой его метод просто перенастроив соединения.
Однако в случае, если в метод run() потребуется передать аргументы, то все сильно усложняется. Т.о. необходимо "настраивать" классы перед использованием и создавать отдельный слот для простого запуска "обработчика". Кроме того, накладывается строгое ограничение, что класс, передаваемый в поток должен быть вовлечен в "сигналы-слоты". Для чистых C++-шных классов это потребует написания обертки, либо рефакторинга кода.

Кроме того, все 2 подхода страдают сложностью с возвращаемым значением, которое можно будет получить только через сигнал.

3-ий подход не страдает ни одним из перечисленных выше недостатков, но цена тому - отсутствие какого-либо контроля.

Интересны ваши мнения.
9
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
19.05.2012, 23:26
Ответы с готовыми решениями:

Два способа решения - два разных ответа
почему-то два разных способа решения задачи приводят к двум разным ответам. Задача такова: ...

два способа, один не работает
Задание: Создайте функцию truncate(str, maxlength), которая проверяет длину строки str, и если она...

Два способа создания методов
Всем привет. Надо сделать лабораторную работу в C# на создание двух одинаковых методов, которые...

Два способа решения задачи
Задача. Решите двумя способами следующую задачу: а.За 16 лопат уплатили 9 руб.60 коп.Сколько...

13
1665 / 1134 / 80
Регистрация: 21.08.2008
Сообщений: 4,734
Записей в блоге: 1
22.05.2012, 09:38 2
че то все смешалось в кучу... ИМХО правильным и масштабируемым решением будет некий гибрид 1-го и 2-го способов, т.е. делаем свой объект потока с нужными нам свойствами, и который может принимать в качестве аргумента другой наш объект (и не только), а всю связь межну ними и остальной логикой сделать на сигналах/слотах
0
385 / 229 / 12
Регистрация: 06.07.2011
Сообщений: 512
22.05.2012, 19:04  [ТС] 3
ну, это и есть первый способ. просто связь с другой частью программы я тут не показывал - она одинакова в обоих случаях.

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

во втором же методе потоки ничего не знают о данных, которые им предстоит обработать и они "всеядны". Если потребуется, то используя второй метод легко создать, скажем, единый пул потоков, который предоставит интерфейс для включения многопоточности в любой точке программы для любых классов без какого-либо дополнительного кода. т.е. реализовать способ №3 руками.
0
1665 / 1134 / 80
Регистрация: 21.08.2008
Сообщений: 4,734
Записей в блоге: 1
22.05.2012, 23:59 4
Цитата Сообщение от Paporotnik Посмотреть сообщение
то это выльется в создание нового сабкласса от QThread. а это не есть хорошо. и хорошо, если подобное изменение впишется в принятую архитектуру.
Зачем? Отдельной нитью работает не сам поток, а то что в него засунуто (в run())
Т.е. сабкласс от QThread у нас один, но передаем туда каждый раз разные классы, иерархию правда придется сделать с виртуальным методом, который и будет в потоке выполняться.
Код
CMyBaseClass
{
virtual void ThreadMetod() = 0;
}:
CMyClass1 public CMyBaseClass
{
void ThreadMetod() {//}
}:
CMyClass2 public CMyBaseClass
{
void ThreadMetod() {//}
 }:
В сабкласс потока передаем нужный указатель (приведенный к виду родителя) и вызываем в run() ThreadMetod().
Как то так можно.
Ну и естетсвенно, что надо будет продумать интерфейс связи сигналов/слотов между потоком и базовым классом.

Добавлено через 1 минуту
PS: ну и QObject::moveToThread() никто не отменял
0
385 / 229 / 12
Регистрация: 06.07.2011
Сообщений: 512
23.05.2012, 01:00  [ТС] 5
иерархию правда придется сделать с виртуальным методом, который и будет в потоке выполняться.
вот-вот. а если нельзя включить в иерархию? или этой иерархии в программе вообще нет, а такой мощный рефакторинг уже не провести?
можно, конечно, передавать в QThread имя вызываемого в потоке метода, список аргументов, потом через мета-объектную информацию этот метод вызывать в run(). Но огород получается внушительный. Стоит ли это того? Если все тоже самое выполняется черех move ToThread
0
oxotnik
23.05.2012, 08:53
  #6

Не по теме:

Цитата Сообщение от Paporotnik Посмотреть сообщение
вот-вот. а если нельзя включить в иерархию? или этой иерархии в программе вообще нет, а такой мощный рефакторинг уже не провести?
ну это, имхо, уже проблема архитектуры кода, и вообще изначально надо думать о возможном дальнейшем расширении, поэтоиу по максимуму закладывать возможности для развития.

0
9 / 9 / 0
Регистрация: 28.02.2011
Сообщений: 45
18.10.2012, 03:46 7
А возможно ли в объекте такого унаследованного класа оперировать объектом, например типа QTextBrowser? Далее более подробно, что я хочу:
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// worker.h
#ifndef WORKER_H
#define WORKER_H
 
#include <QThread>
#include <QDebug>
#include <QTextBrowser>
#include <QFile>
#include <QString>
 
class Worker : public QThread
{
public:
    Worker();
    void SetObject(QTextBrowser *i_textbrowser);
    void run();
    void OpenFile();
private:
    void read_and_output();
    QTextBrowser *m_textbrowser;
    QFile m_debug_file;
};
#endif // WORKER_H
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// worker.cpp
#include "worker.h"
 
Worker::Worker() {
}
 
void Worker::OpenFile() {
    m_debug_file.setFileName("debug");
    if(m_debug_file.open(QIODevice::ReadOnly))
        qDebug() << "Open file!!!";
    else
        qDebug() << "Can't open debug file...";
}
 
void Worker::read_and_output() {
    QString l_all_strings = m_debug_file.readAll();
    qDebug() << l_all_strings;
    m_textbrowser->setText(l_all_strings);
    m_textbrowser->reload();
}
 
void Worker::run() {
    OpenFile();
    for (int i = 0; i < 100; i++) {
        msleep(300);
        read_and_output();
    }
}
 
void Worker::SetObject(QTextBrowser *i_textbrowser) {
    m_textbrowser = i_textbrowser;
}
C++ (Qt)
1
2
3
4
5
6
7
8
//mainwindow.cpp
//...
void MainWindow::on_pushButton_clicked() {
    //w - объект типа worker!!!
    w.SetObject(ui->textBrowser);
    w.start();
}
//...
При выполнении всего этого будет следующая ошибка:
Bash
1
2
3
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTextDocument(0x86ca6a8), parent's thread is QThread(0x8582470), current thread is QThread(0xbff905a4)
The program has unexpectedly finished.
Возможно ли как то решить такую задачу? Спасибо!
0
1665 / 1134 / 80
Регистрация: 21.08.2008
Сообщений: 4,734
Записей в блоге: 1
18.10.2012, 08:54 8
Цитата Сообщение от filkloch Посмотреть сообщение
Возможно ли как то решить такую задачу? Спасибо!
Нельзя гуйней напрямую руководить из дочернего (не гуевого) потока. Посылай сигналы из потока с нужными данными, а в основном гуевом потоке эти сигналы принимай и обрабатывай.
2
1443 / 1326 / 131
Регистрация: 20.03.2009
Сообщений: 4,689
Записей в блоге: 11
18.10.2012, 23:36 9
Цитата Сообщение от Paporotnik Посмотреть сообщение
оздать, скажем, единый пул потоков,
Ничего создавать не надо, есть QThreadPool
Цитата Сообщение от Paporotnik Посмотреть сообщение
QThread имя вызываемого в потоке метода, список аргументов, потом через мета-объектную информацию
Толсто, смотри на обобщенные функторы Александреску, Gof паттерн Комманда(Command), а в C++11 есть чудесные лямбда выражения.

По поводу moveToThread, то есть классы, экземпляры которых могут использоваться только в том потоке, в котором он создан.
0
51 / 149 / 33
Регистрация: 29.06.2019
Сообщений: 1,428
02.10.2020, 08:22 10
Цитата Сообщение от Paporotnik Посмотреть сообщение
#include <QtConcurrentRun>
почему-то нет в v5.12 (а может у меня build такой)?...
хотя видела
don't forget that Concurrent::run can hung sometimes with no reason in pre-5.3 builds.
в любом случае - спасибо за примеры - в принципе компилится и так... не знаю, имеет ли существенную роль этот include здесь

Добавлено через 15 минут
нашла пример здесь... проходит такой #include <QtConcurrent/QtConcurrent>
0
13 / 13 / 1
Регистрация: 19.10.2019
Сообщений: 607
22.03.2021, 16:28 11
Вот читаю эту тему девятилетней давности и статью https://habr.com/ru/post/150274/
и не могу понять: что хорошего в способе 2, если класс TestClass необходимо оставлять без родителя и он не будет иметь тех гарантий очистки памяти, как и остальные.
0
683 / 458 / 160
Регистрация: 01.10.2015
Сообщений: 1,264
22.03.2021, 17:00 12
Цитата Сообщение от squareroot Посмотреть сообщение
что хорошего в способе 2
Есть статья Threads Events QObjects в Qt-вики, найдите раздел Feature comparison, он состоит из элементарной таблички, наглядно демонстрирующей разницу между подходами. Следует понимать, что это разные инструменты, и как любой инструмент, они имеют свои ограничения и области применимости.

гарантий очистки памяти
Предусмотреть сигнал, означающий завершение выполнения полезной работы экземпляром worker, подключить этот сигнал к соответствующим слотам, которые будут корректно завершать поток, удалять "работягу", и удалять конкретный поток.
C++ (Qt)
1
2
3
4
5
6
// прекращаем обработку event loop потока
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
// запланировали отложенное убийство "работяги"
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
// запланировали отложенное убийство потока
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
Если вам нужна обработка цикла сообщений, и возможность выполнения с другим приоритетом, то три коннекта - не столь уж большая цена.
0
13 / 13 / 1
Регистрация: 19.10.2019
Сообщений: 607
22.03.2021, 17:38 13
0x90h,
В табличке сравниваются не эти способы.
Подозреваю, что Вы меня не поняли.
При использовании movetothread мы должны передавать объекты у которых нет родителя (свойство parent не установлено)
В первом же случае, нам не нужно использовать moveToThread, поэтому TestClass может установить себе родителя, хотя автор этим пренебрег.

Добавлено через 25 минут
Петля событий при первом варианте может быть, а при втором скорей всего тоже(хотя я не пробовал ни разу).
Третий способ, я не рассматриваю.
0
Алексей1153
22.03.2021, 19:42     Два способа использования QThread
  #14

Не по теме:

squareroot, с моей точки зрения самый удобный (а для меня - и самый правильный) - это способ №1. Только у ТС косяк - run должен быть "бесконечным циклом" + обработка петли сообщений ( QThread::eventDispatcher() или exec() )

0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
22.03.2021, 19:42

Два способа считывать данные от пользователя
Здравствуйте. Начал изучать Java и интересует один вопрос. Есть два способа считать данные от...

Написать еще два способа решения
int c = 0, k; Console.WriteLine(&quot;Введите число&quot;); k =...

Два способа работы с базой данных ?
Здравствуйте, уважаемые Знатоки! Насколько я понял существует два способа работы с базой данных...

Зачем существует два способа обращения к объектам?
Здравствуйте. Зачем существует два способа обращения к объектам: по ссылке и по указателю?


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
14
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru