Qt线程 – 继承QObject方式

Qt线程 – 继承QObject方式

Qt使用线程有两种方式,在新版本的Qt中,Qt官方推荐使用继承QObject的方式,本文档记录使用此方法线程的实验过程线程

实验总结

在实验1 至实验5终于得出结论,使用继承QObject的方式实现多线程的方式,
git源码:https://gitee.com/ALONE_WORK/QtCeShiXiangMu.git
1. 使用继承QObject的方式实现多线程的方式经过实验证明是可以的,具体过程请仔细观察实验过程记录;
2. 本次实验中实验了终止线程的一种方式,通过变量来跳出循环线程;
3. 在使用变量共享的方式退出线程是使用QMutexLocker的方式进行变量保护;

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
33
34
35
36
37
class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

官方示例

从示例中可以看出,总结一下步骤:
1. 新建一个类并继承QObject基类;
2. 在主线程中new出这个新建的类,新类不能有父类,再声明一个QThread类,并将类moveToThread到线程中;
3. 通过start开始线程;
4. 退出线程时需要退出线程;

实验过程记录

首先按照Qt官方给出的示例做实验,然后进行各种扩展实验。

实验1 – 官方实验

1. 首先新建一个QWidget工程;
2. 新建一个类并继承QObject类,添加两个线程,一个是执行线程槽函数,还有一个结束线程槽函数;
3. 主界面线程中添加两个按钮,开始/停止线程按钮,添加两个信号,开始/停止线程;
4. 具体代码如下:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
------------------thread1.h------------------
#ifndef THREAD1_H
#define THREAD1_H

#include <QObject>
#include <QThread>

class Thread1 : public QObject
{
    Q_OBJECT
public:
    explicit Thread1(QObject *parent = nullptr);

signals:

public slots:
    void doWork();
    void Stop();
};

#endif // THREAD1_H

------------------thread1.cpp------------------
#include "thread1.h"
#include <QDebug>
#include <QDateTime>

Thread1::Thread1(QObject *parent) : QObject(parent)
{

}


void Thread1::doWork()
{
    qDebug() << "Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
}

void Thread1::Stop()
{
    qDebug() << "Stop Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
}

------------------widget.h------------------
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QThread>
#include "thread1.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

signals:
    void StartThread();
    void StopThread();

private slots:
    // 开始线程按钮
    void on_pushButton_clicked();

    // 停止线程按钮
    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;

    Thread1 *Td1;
    QThread Thread;
};

#endif // WIDGET_H

------------------widget.cpp------------------

#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    Td1 = new Thread1;
    Td1->moveToThread(&Thread);
    connect(&Thread, &QThread::finished, Td1, &QObject::deleteLater);
    connect(this, SIGNAL(StartThread()), Td1, SLOT(doWork()));
    connect(this, SIGNAL(StopThread()), Td1, SLOT(Stop()));
    Thread.start();
}

Widget::~Widget()
{
    delete ui;
    delete Td1;
    Thread.quit();
    Thread.wait();
}

void Widget::on_pushButton_clicked()
{
    emit StartThread();
}

void Widget::on_pushButton_2_clicked()
{
    emit StopThread();
}

运行结果

先点开始线程,再点停止线程按钮
Thread: “2018-11-09 11:25:27.886”
Stop Thread: “2018-11-09 11:25:29.206”

分析

完全按照Qt官方操作方法是可以运行的没有问题,但是实际用到时候不是这么简单,所以需要更多的实验来支持;所以在接下来的实验中就是在Qt官方方法的基础进行实验

 实验2 – 扩展实验1

在实验1的基础上稍微进行扩展一下

1. 在原来的基础上添加一条语句:QThread::sleep(2);
2. 在操作的时候需要在线程休眠结束之前点击停止线程;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "thread1.h"
#include <QDebug>
#include <QDateTime>

Thread1::Thread1(QObject *parent) : QObject(parent)
{

}


void Thread1::doWork()
{
    qDebug() << "Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
    QThread::sleep(2); // 在线程的最后添加2秒的休眠;
}

void Thread1::Stop()
{
    qDebug() << "Stop Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
}

 

 实验2 运行结果

Thread: “2018-11-09 11:38:27.399” Stop Thread: “2018-11-09 11:38:29.400” Stop Thread: “2018-11-09 11:38:29.400”

注意:我是在点击开始按钮后马上点击结束按钮的

实验2 结果分析

可以再实验结果中看出,从执行线程结束2s后才执行了结束线程槽函数,那么我们就很容得出结论:这样的线程只能一个线程在运行,其中如果有其他信号只能等到线程结束后才能接收到 #### 实验3 – 扩展实验2 > 由于之前在一片博客中看到有人在一个类中使用多个线程,然后我就实验了一下,试试证明那样是不行的,并且还在线程中使用while(1)这样的死循环,这样就这个线程就只能专心的执行一个线程了。

1. 在实验2的基础上继续扩展,再添加一个槽函数做为线程2
2. 并且在点击开始执行线程后,迅速点击停止按钮;

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
------------------thread1.h------------------
#ifndef THREAD1_H
#define THREAD1_H

#include <QObject>
#include <QThread>

class Thread1 : public QObject
{
    Q_OBJECT
public:
    explicit Thread1(QObject *parent = nullptr);

signals:

public slots:
    void doWork();
    void doWork2();
    void Stop();
};

#endif // THREAD1_H

------------------thread1.cpp------------------
#include "thread1.h"
#include <QDebug>
#include <QDateTime>

Thread1::Thread1(QObject *parent) : QObject(parent)
{

}


void Thread1::doWork()
{
    qDebug() << "Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
    QThread::sleep(2);
}

void Thread1::doWork2()
{
    qDebug() << "Thread2: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
    QThread::sleep(2);
}

void Thread1::Stop()
{
    qDebug() << "Stop Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
}

#ifndef WIDGET_H
#define WIDGET_H

------------------widget.h------------------
#include <QWidget>
#include <QThread>
#include "thread1.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

signals:
    void StartThread();
    void StartThread2();
    void StopThread();

private slots:
    // 开始线程按钮
    void on_pushButton_clicked();

    // 停止线程按钮
    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;

    Thread1 *Td1;
    QThread Thread;
};

#endif // WIDGET_H

------------------widget.cpp------------------
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    Td1 = new Thread1;
    Td1->moveToThread(&Thread);
    connect(&Thread, &QThread::finished, Td1, &QObject::deleteLater);
    connect(this, SIGNAL(StartThread()), Td1, SLOT(doWork()));
    connect(this, SIGNAL(StartThread2()), Td1, SLOT(doWork2()));
    connect(this, SIGNAL(StopThread()), Td1, SLOT(Stop()));
    Thread.start();
}

Widget::~Widget()
{
    delete ui;
    delete Td1;
    Thread.quit();
    Thread.wait();
}

void Widget::on_pushButton_clicked()
{
    emit StartThread();
    emit StartThread2();
}

void Widget::on_pushButton_2_clicked()
{
    emit StopThread();
}

实验3 运行结果

Thread: “2018-11-09 11:54:26.358”
Thread2: “2018-11-09 11:54:28.360”
Stop Thread: “2018-11-09 11:54:30.361”
注意:我是在点击开始按钮后马上点击结束按钮的

实验3结果分析

经过以上实验可以得出,这种一个方式一个类只能当做一个线程来使用,并且还有以问题就是如果在以上实验中想要使用while(1)死循环也不好用,因为没有办法暂停和停止线程,那么这个暂停和停止的实验就需要进一步进行实验了。

实验4 – 扩展实验3

以上实验了单线程,那要想实现多线程相比就要使用多个类进行实验,接下来就是多线程的实验

1. 在实验2的基础上添加一个新的类,并继承QObject类,新类与实验2中的类容一样;
2. 目的是想验证两个线程能不能同时执行;

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
------------------widget.h------------------
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QThread>
#include "thread1.h"
#include "parsethread.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

signals:
    void StartThread();
    void StartThread2();
    void StopThread();

private slots:
    // 开始线程按钮
    void on_pushButton_clicked();

    // 停止线程按钮
    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;

    Thread1 *Td1;
    ParseThread *Td2;
    QThread Thread;
};

#endif // WIDGET_H

------------------widget.cpp------------------
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    Td1 = new Thread1;
    Td1->moveToThread(&Thread);
    Td2 = new ParseThread;
    Td2->moveToThread(&Thread);
    connect(&Thread, &QThread::finished, Td1, &QObject::deleteLater);
    connect(&Thread, &QThread::finished, Td2, &QObject::deleteLater);
    connect(this, SIGNAL(StartThread()), Td1, SLOT(doWork()));
    connect(this, SIGNAL(StartThread2()), Td2, SLOT(doWork()));
    connect(this, SIGNAL(StopThread()), Td1, SLOT(Stop()));
    connect(this, SIGNAL(StopThread()), Td2, SLOT(Stop()));
    Thread.start();
}

Widget::~Widget()
{
    delete ui;
    delete Td1;
    delete Td2;
    Thread.quit();
    Thread.wait();
}

void Widget::on_pushButton_clicked()
{
    emit StartThread();
    emit StartThread2();
}

void Widget::on_pushButton_2_clicked()
{
    emit StopThread();
}

实验4 运行结果

Thread: “2018-11-09 13:37:13.746”
Parse thread: “2018-11-09 13:37:15.748”
Stop Thread: “2018-11-09 13:37:17.749”
Stop parse hread: “2018-11-09 13:37:17.749”
注意:此实验也是点击开始后点击停止

实验4结果分析

按照实验结果分析,就算是使用两个类进行实验,两个线程也是不能同时运行,但是在主线程中只声明一个QThread,所以接下来只能通过声明两个来进行实验。

实验5 – 扩展实验4

经过以上实验证明,以上的方法线程都不能同时运行,所以需要在实验4的基础上进一步进行实验了

1. 在主线程中再声明一个QThread对象;

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
------------------widget.h------------------
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QThread>
#include "thread1.h"
#include "parsethread.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

signals:
    void StartThread();
    void StartThread2();
    void StopThread();

private slots:
    // 开始线程按钮
    void on_pushButton_clicked();

    // 停止线程按钮
    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;

    Thread1 *Td1;
    ParseThread *Td2;
    // 实验证明一个线程需要对应声明一个QThread对象
    QThread Thread;
    QThread Thread2;
};

#endif // WIDGET_H

------------------widget.cpp------------------
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    Td1 = new Thread1;
    Td1->moveToThread(&Thread);
    Td2 = new ParseThread;
    Td2->moveToThread(&Thread2);
    connect(&Thread, &QThread::finished, Td1, &QObject::deleteLater);
    connect(&Thread2, &QThread::finished, Td2, &QObject::deleteLater);
    connect(this, SIGNAL(StartThread()), Td1, SLOT(doWork()));
    connect(this, SIGNAL(StartThread2()), Td2, SLOT(doWork()));
    connect(this, SIGNAL(StopThread()), Td1, SLOT(Stop()));
    connect(this, SIGNAL(StopThread()), Td2, SLOT(Stop()));
    Thread.start();
    Thread2.start();
}

Widget::~Widget()
{
    delete ui;
    delete Td1;
    delete Td2;
    Thread.quit();
    Thread.wait();
    Thread2.quit();
    Thread2.wait();
}

void Widget::on_pushButton_clicked()
{
    emit StartThread();
    emit StartThread2();
}

void Widget::on_pushButton_2_clicked()
{
    emit StopThread();
}

实验5 运行结果

Thread: “2018-11-09 13:47:53.092”
Parse thread: “2018-11-09 13:47:53.092”
Stop Thread: “2018-11-09 13:47:59.267”
Stop parse hread: “2018-11-09 13:47:59.267”

实验5 结果分析

通过以上实验终于得出多线程实验的方式,通过每个线程对应一个QThread对象可以达到多线程的实现目的,但是现在任然面临一个问题,在线程运行的时候如何对线程进行暂停或者停止,这个需要进一步的进行实验

线程终止实验

如果一个线程运行完成,就会结束。可很多情况并非这么简单,由于某种特殊原因,当线程还未执行完时,我们就想中止它,但是往往不恰当的终止会引起一些位置的错误,比如当关闭主界面的时候,很有可能次线程正在运行,这时,就会出现如下提示QThread: Destroyed while thread is still running; 这样程序就是不合理和退出。

实验6 – 线程终止实验1

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
------------------thread1.h------------------
#ifndef THREAD1_H
#define THREAD1_H

#include <QObject>
#include <QThread>

class Thread1 : public QObject
{
    Q_OBJECT
public:
    explicit Thread1(QObject *parent = nullptr);

    void Stop();

signals:

public slots:
    void doWork();

private:
    bool IsStop;
};

#endif // THREAD1_H

------------------thread1.cpp------------------
#include "thread1.h"
#include <QDebug>
#include <QDateTime>

Thread1::Thread1(QObject *parent) : QObject(parent)
{
    IsStop = false;
}


void Thread1::doWork()
{
    IsStop = false;
    while(1) {
        if(IsStop)
            break;
        qDebug() << this->thread()->currentThreadId() << ": " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
        QThread::sleep(1);
    }
    qDebug() << "Thread end...";
}

void Thread1::Stop()
{
    IsStop = true;
    qDebug() << "Stop Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
}

------------------widget.cpp------------------
void Widget::on_pushButton_2_clicked()
{
    Td1->Stop();
    qDebug() << "thread end.";
}

实验6 运行结果

0x4da8 : “2018-11-09 14:41:57.856”
0x4da8 : “2018-11-09 14:41:58.857”
0x4da8 : “2018-11-09 14:41:59.858”
Stop Thread: “2018-11-09 14:42:00.014”
thread end.
Thread end…
注意:在实验6中,只是将Stop函数改为普通函数,并添加一个变量

实验6 结果分析

可以从运行的结果中看出,线程一直处于运行的状态,通过调用Stop()函数来改变变量属性,可以直接这样就可以退出了线程,但是细心的人可以看到代码中变量线程和函数都在使用,但是并没有加任何的保护,这样实际上是不合理的;所以接下来的实验便是要给变量加上保护。

实验7 – 线程终止实验2

在使用线程时加线程锁都是必要的,但是使用线程锁也是比较危险的,因为一旦死锁,程序也就死了,先给一段正确的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
------------------thread.cpp------------------
void Thread1::doWork()
{
    IsStop = false;
    while(1) {
        QMutexLocker locker(&StopMutex);
        if(IsStop)
            break;
        qDebug() << this->thread()->currentThreadId() << ": " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
        QThread::sleep(1);
    }
    qDebug() << "Thread end...";
}

void Thread1::Stop()
{
    QMutexLocker locker(&StopMutex);
    IsStop = true;
    qDebug() << "Stop Thread: " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
}

实验7 运行结果

0x3f40 : “2018-11-09 15:19:52.937”

0x3f40 : “2018-11-09 15:19:53.937”

0x3f40 : “2018-11-09 15:19:54.938”

Stop Thread: “2018-11-09 15:19:55.939”

thread end.

Thread end…

0x3f40 : “2018-11-09 15:19:57.624”

0x3f40 : “2018-11-09 15:19:58.625”

Stop Thread: “2018-11-09 15:19:59.626”

thread end.

Thread end…

注意:在此次实验中进行两次操作,开始->停止,开始->停止;

实验7 结果分析

在Qt官网也可以查到,线程锁有两种,QMutex和QMutexLocker,而QMutexLocker是相对简单的用法,一般用于局部,不用考虑解锁,释放锁等问题;但是使用的时候也要注意放位置
例如:如果将下列的锁放在函数的最开始,那么就会造成死锁,这是在我实验中遇到的问题,因为QMutexLocker作用在局部,所以讲QMutexLocker放在函数的开头,而下面是一个while(1)死循环,那么这个锁开始以后就无法结束,也就造成了死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
------------------thread.cpp------------------
void Thread1::doWork()
{
    // 注:错误用法,会造成死锁
    QMutexLocker locker(&StopMutex);
    IsStop = false;
    while(1) {
        if(IsStop)
            break;
        qDebug() << this->thread()->currentThreadId() << ": " << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
        QThread::sleep(1);
    }
    qDebug() << "Thread end...";
}

Qt线程 – 继承QObject方式》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注