1. 多线程的使用
在进行桌面应用程序开发的时候, 假设应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。这种情况下就需要使用多线程,其中一个线程处理窗口事件,其他线程进行逻辑运算,多个线程各司其职,不仅可以提高用户体验还可以提升程序的执行效率。
在qt中使用了多线程,有些事项是需要额外注意的:
- 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
- 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
- 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制
1.1 线程类 QThread
Qt中提供了一个线程类,通过这个类就可以创建子线程了,Qt中一共提供了两种创建子线程的方式,后边会依次介绍其使用方式。看一下这个类中提供的一些常用API函数:
1.1.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
|
QThread::QThread(QObject *parent = Q_NULLPTR);
bool QThread::isFinished() const;
bool QThread::isRunning() const;
Priority QThread::priority() const; void QThread::setPriority(Priority priority); 优先级: QThread::IdlePriority --> 最低的优先级 QThread::LowestPriority QThread::LowPriority QThread::NormalPriority QThread::HighPriority QThread::HighestPriority QThread::TimeCriticalPriority --> 最高的优先级 QThread::InheritPriority --> 子线程和其父线程的优先级相同, 默认是这个
void QThread::exit(int returnCode = 0);
bool QThread::wait(unsigned long time = ULONG_MAX);
|
1.1.2 信号槽
1 2 3 4 5 6 7 8 9 10 11 12 13
|
[slot] void QThread::quit();
[slot] void QThread::start(Priority priority = InheritPriority);
[slot] void QThread::terminate();
[signal] void QThread::finished();
[signal] void QThread::started();
|
1.1.3 静态函数
1 2 3 4 5 6 7 8
| [static] QThread *QThread::currentThread();
[static] int QThread::idealThreadCount();
[static] void QThread::msleep(unsigned long msecs); [static] void QThread::sleep(unsigned long secs); [static] void QThread::usleep(unsigned long usecs);
|
1.1.4 任务处理函数
1 2
| [virtual protected] void QThread::run();
|
这个run()
是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread
,并且在子类中重写父类的run()
方法,函数体就是对应的任务处理流程。另外,这个函数是一个受保护的成员函数,不能够在类的外部调用,如果想要让线程执行这个函数中的业务流程,需要通过当前线程对象调用槽函数start()
启动子线程,当子线程被启动,这个run()
函数也就在线程内部被调用了。
1.2 使用方式1
1.2.1 操作步骤
Qt中提供的多线程的第一种使用方式的特点是: 简单。操作步骤如下:
- 需要创建一个线程类的子类,让其继承QT中的线程类 QThread,比如:
1 2 3 4
| class MyThread:public QThread { ...... }
|
- 重写父类的 run() 方法,在该函数内部编写子线程要处理的具体的业务流程
1 2 3 4 5 6 7 8 9
| class MyThread:public QThread { ...... protected: void run() { ........ } }
|
- 在主线程中创建子线程对象,new 一个就可以了
1
| MyThread * subThread = new MyThread;
|
- 启动子线程, 调用 start() 方法
不能在类的外部调用run() 方法启动子线程,在外部调用start()相当于让run()开始运行
当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意:
- 在Qt中在子线程中不要操作程序中的窗口类型对象, 不允许, 如果操作了程序就挂了
- 只有主线程才能操作程序中的窗口对象, 默认的线程就是主线程, 自己创建的就是子线程
1.2.2 示例代码
举一个简单的数数的例子,假如只有一个线程,让其一直数数,会发现数字并不会在窗口中时时更新,并且这时候如果用户使用鼠标拖动窗口,就会出现无响应的情况,使用多线程就不会出现这样的现象了。
点击按钮开始在子线程中数数,让后通过信号槽机制将数据传递给UI线程,通过UI线程将数据更新到窗口中。

mythread.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #ifndef MYTHREAD_H #define MYTHREAD_H
#include <QThread>
class MyThread : public QThread { Q_OBJECT public: explicit MyThread(QObject *parent = nullptr);
protected: void run();
signals: void curNumber(int num);
public slots: };
#endif
|
mythread.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include "mythread.h" #include <QDebug>
MyThread::MyThread(QObject *parent) : QThread(parent) {
}
void MyThread::run() { qDebug() << "当前线程对象的地址: " << QThread::currentThread();
int num = 0; while(1) { emit curNumber(num++); if(num == 10000000) { break; } QThread::usleep(1); } qDebug() << "run() 执行完毕, 子线程退出..."; }
|
mainwindow.cpp
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
| #include "mainwindow.h" #include "ui_mainwindow.h" #include "mythread.h" #include <QDebug>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this);
qDebug() << "主线程对象地址: " << QThread::currentThread(); MyThread* subThread = new MyThread;
connect(subThread, &MyThread::curNumber, this, [=](int num) { ui->label->setNum(num); });
connect(ui->startBtn, &QPushButton::clicked, this, [=]() { subThread->start(); }); }
MainWindow::~MainWindow() { delete ui; }
|
这种在程序中添加子线程的方式是非常简单的,但是也有弊端
假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。
1.3 使用方式2
1.3.1 操作步骤
Qt提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些,其具体操作步骤如下:
- 创建一个新的类,让这个类从QObject派生
1 2 3 4
| class MyWork:public QObject { ....... }
|
- 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
1 2 3 4 5 6 7
| class MyWork:public QObject { public: ....... void working(); }
|
- 在主线程中创建一个QThread对象, 这就是子线程的对象
1
| QThread* sub = new QThread;
|
- 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
1 2
| MyWork* work = new MyWork(this); MyWork* work = new MyWork;
|
- 将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法
1 2 3 4
|
work->moveToThread(sub);
|
启动子线程,调用 start()
, 这时候线程启动了, 但是移动到线程中的对象并没有工作
调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的
1.3.2 示例代码
假设函数处理上面在程序中数数的这个需求,具体的处理代码如下:
mywork.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #ifndef MYWORK_H #define MYWORK_H
#include <QObject>
class MyWork : public QObject { Q_OBJECT public: explicit MyWork(QObject *parent = nullptr);
void working();
signals: void curNumber(int num);
public slots: };
#endif
|
mywork.cpp
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
| #include "mywork.h" #include <QDebug> #include <QThread>
MyWork::MyWork(QObject *parent) : QObject(parent) {
}
void MyWork::working() { qDebug() << "当前线程对象的地址: " << QThread::currentThread();
int num = 0; while(1) { emit curNumber(num++); if(num == 10000000) { break; } QThread::usleep(1); } qDebug() << "run() 执行完毕, 子线程退出..."; }
|
mainwindow.cpp
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
| #include "mainwindow.h" #include "ui_mainwindow.h" #include <QThread> #include "mywork.h" #include <QDebug>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this);
qDebug() << "主线程对象的地址: " << QThread::currentThread();
QThread* sub = new QThread; MyWork* work = new MyWork; work->moveToThread(sub); sub->start(); connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working); connect(work, &MyWork::curNumber, this, [=](int num) { ui->label->setNum(num); }); }
MainWindow::~MainWindow() { delete ui; }
|
使用这种多线程方式,假设有多个不相关的业务流程需要被处理,那么就可以创建多个类似于MyWork
的类,将业务流程放多类的公共成员函数中,然后将这个业务类的实例对象移动到对应的子线程中moveToThread()
就可以了,这样可让编写的程序更灵活,可读性强,易于维护。
2. 线程池的使用
2.1 QRunnable
在Qt中使用线程池需要先创建任务,添加到线程池中的每一个任务都需要是一个QRunnable
类型,因此在程序中需要创建子类继承QRunnable
这个类,然后重写 run()
方法,在这个函数中编写要在线程池中执行的任务,并将这个子类对象传递给线程池,这样任务就可以被线程池中的某个工作的线程处理掉了。
QRunnable
类 常用函数不多,主要是设置任务对象传给线程池后,是否需要自动析构。
1 2 3 4 5 6 7 8
| [pure virtual] void QRunnable::run();
void QRunnable::setAutoDelete(bool autoDelete);
bool QRunnable::autoDelete() const;
|
创建一个要添加到线程池中的任务类,处理方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MyWork : public QObject, public QRunnable { Q_OBJECT public: explicit MyWork(QObject *parent = nullptr) { setAutoDelete(true); } ~MyWork();
void run() override{} }
|
在上面的示例中MyWork
类是一个多重继承,如果需要在这个任务中使用Qt的信号槽机制进行数据的传递就必须继承QObject
这个类,如果不使用信号槽传递数据就可以不继承了,只继承QRunnable
即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MyWork :public QRunnable { Q_OBJECT public: explicit MyWork() { setAutoDelete(true); } ~MyWork();
void run() override{} }
|
2.2 QThreadPool
Qt中的 QThreadPool
类管理了一组 QThreads
, 里边还维护了一个任务队列。QThreadPool 管理和回收各个 QThread
对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局 QThreadPool
对象,可以通过调用 globalInstance()
来访问它。也可以单独创建一个 QThreadPool
对象使用。
线程池常用的API函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int maxThreadCount() const; void setMaxThreadCount(int maxThreadCount);
void QThreadPool::start(QRunnable * runnable, int priority = 0);
bool QThreadPool::tryStart(QRunnable * runnable);
int QThreadPool::activeThreadCount() const;
bool QThreadPool::tryTake(QRunnable *runnable);
void QThreadPool::clear();
static QThreadPool * QThreadPool::globalInstance();
|
一般情况下,我们不需要在Qt程序中创建线程池对象,直接使用Qt为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用start()
方法就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。
具体的使用方式如下:
mywork.h
1 2 3 4 5 6 7 8 9
| class MyWork :public QRunnable { Q_OBJECT public: explicit MyWork(); ~MyWork();
void run() override; }
|
mywork.cpp
1 2 3 4 5 6 7 8 9 10
| MyWork::MyWork() : QRunnable() { setAutoDelete(true); } void MyWork::run() { ...... }
|
mainwindow.cpp
1 2 3 4 5 6 7 8 9 10 11 12
| MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this);
QThreadPool::globalInstance()->setMaxThreadCount(4); MyWork* task = new MyWork; QThreadPool::globalInstance()->start(task); }
|
3. 套接字通信
在标准C++没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于C的API函数,基于这些C的API函数我们也可以封装自己的C++类 。
但是Qt不一样,它是C++的一个框架并且里边提供了用于套接字通信的类(TCP、UDP)这样就使得我们的操作变得更加简单了(当然,在Qt中使用标准C的API进行套接字通信也是完全没有问题的)。
使用Qt提供的类进行基于TCP的套接字通信需要用到两个类:
- QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接。
- QTcpSocket:通信的套接字类,客户端、服务器端都需要使用。
这两个套接字通信类都属于网络模块network。
3.1 QTcpServer
QTcpServer
类用于监听客户端连接以及和客户端建立连接,在使用之前先介绍一下这个类提供的一些常用API函数:
3.1.1 公共成员函数
构造函数
1
| QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);
|
给监听的套接字设置监听
1 2 3 4 5 6 7
| bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
bool QTcpServer::isListening() const;
QHostAddress QTcpServer::serverAddress() const;
quint16 QTcpServer::serverPort() const
|
- 参数:
- address:通过类QHostAddress可以封装IPv4、IPv6格式的IP地址,QHostAddress::Any表示自动绑定
- port:如果指定为0表示随机绑定一个可用端口。
- 返回值:绑定成功返回true,失败返回false
得到和客户端建立连接之后用于通信的QTcpSocket
套接字对象,它是QTcpServer
的一个子对象,当QTcpServer
对象析构的时候会自动析构这个子对象,当然也可自己手动析构,建议用完之后自己手动析构这个通信的QTcpSocket
对象。
1
| QTcpSocket *QTcpServer::nextPendingConnection();
|
阻塞等待客户端发起的连接请求,不推荐在单线程程序中使用,建议使用非阻塞方式处理新连接,即使用信号 newConnection() 。
1
| bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);
|
- 参数:
- msec:指定阻塞的最大时长,单位为毫秒(ms)
- timeout:传出参数,如果操作超时timeout为true,没有超时timeout为false
3.1.2 信号
当接受新连接导致错误时,将发射如下信号。socketError参数描述了发生的错误相关的信息。
1
| [signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);
|
每次有新连接可用时都会发出 newConnection() 信号。
1
| [signal] void QTcpServer::newConnection();
|
3.2 QTcpSocket
QTcpSocket
是一个套接字通信类,不管是客户端还是服务器端都需要使用。在Qt中发送和接收数据也属于IO操作(网络IO),先看这个类的继承关系:

3.2.1 公共成员函数
构造函数
1
| QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);
|
连接服务器,需要指定服务器端绑定的IP和端口信息
。
1 2 3
| [virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);
|
在Qt中不管调用读操作函数接收数据,还是调用写函数发送数据,操作的对象都是本地的由Qt框架维护的一块内存。因此,调用了发送函数数据不一定会马上被发送到网络中,调用了接收函数也不是直接从网络中接收数据,关于底层的相关操作是不需要使用者来维护的。
接收数据
1 2 3 4 5 6
| qint64 QIODevice::read(char *data, qint64 maxSize);
QByteArray QIODevice::read(qint64 maxSize);
QByteArray QIODevice::readAll();
|
发送数据
1 2 3 4 5 6
| qint64 QIODevice::write(const char *data, qint64 maxSize);
qint64 QIODevice::write(const char *data);
qint64 QIODevice::write(const QByteArray &byteArray);
|
3.2.2 信号
在使用QTcpSocket
进行套接字通信的过程中,如果该类对象发射出readyRead()
信号,说明对端发送的数据达到了,之后就可以调用read 函数
接收数据了。
1
| [signal] void QIODevice::readyRead();
|
调用connectToHost()
函数并成功建立连接之后发出connected()
信号。
1
| [signal] void QAbstractSocket::connected();
|
在套接字断开连接时发出disconnected()
信号。
1
| [signal] void QAbstractSocket::disconnected();
|
3.3 通信流程
使用Qt提供的类进行套接字通信比使用标准C API进行网络通信要简单(因为在内部进行了封装) Qt中的套接字通信流程如下:
3.3.1 服务器端
3.3.1.1 通信流程
- 创建套接字服务器
QTcpServer
对象
- 通过
QTcpServer
对象设置监听,即:QTcpServer::listen()
- 基于
QTcpServer::newConnection()
信号检测是否有新的客户端连接
- 如果有新的客户端连接调用
QTcpSocket *QTcpServer::nextPendingConnection()
得到通信的套接字对象
- 使用通信的套接字对象
QTcpSocket
和客户端进行通信
3.3.1.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
| #ifndef MAINWINDOW_H #define MAINWINDOW_H
#include <QMainWindow> #include <QTcpServer> #include <QTcpSocket> #include <QLabel>
QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE
class MainWindow : public QMainWindow { Q_OBJECT
public: MainWindow(QWidget *parent = nullptr); ~MainWindow();
private slots:
void on_startBtn_clicked();
void on_sendBtn_clicked();
private: Ui::MainWindow *ui; QTcpServer* m_server; QTcpSocket* m_tcp; QLabel* m_status; }; #endif
|
源文件
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
| #include "mainwindow.h" #include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
setWindowTitle("Tcp - Server");
m_server = new QTcpServer(this);
connect(m_server,&QTcpServer::newConnection,this,[=](){ m_tcp = m_server->nextPendingConnection(); ui->record->append("connect sucessfully"); m_status->setPixmap(QPixmap(":/bingo").scaled(20,20));
connect(m_tcp,&QTcpSocket::readyRead,this,[=](){ QString recvmsg = m_tcp->readAll(); ui->record->append("client say : " + recvmsg); });
connect(m_tcp,&QTcpSocket::disconnected,this,[=](){ ui->record->append("disconnect..."); m_tcp->close(); m_tcp->deleteLater(); m_status->setPixmap(QPixmap(":/wrong").scaled(20,20)); }); }); m_status = new QLabel; m_status->setPixmap(QPixmap(":/wrong").scaled(20,20)); ui->statusbar->addWidget(new QLabel("connect status : ")); ui->statusbar->addWidget(m_status); }
MainWindow::~MainWindow() { delete ui; }
void MainWindow::on_startBtn_clicked() { unsigned short port = ui->port->text().toInt();
m_server->listen(QHostAddress::Any,port); ui->startBtn->setEnabled(false); }
void MainWindow::on_sendBtn_clicked() { QString sendmsg = ui->msg->toPlainText(); m_tcp->write(sendmsg.toUtf8()); ui->record->append("server say : " +sendmsg); ui->msg->clear(); }
|
3.3.2 客户端
3.3.2.1 通信流程
- 创建通信的套接字类
QTcpSocket
对象
- 使用服务器端绑定的IP和端口连接服务器
QAbstractSocket::connectToHost()
- 使用
QTcpSocket
对象和服务器进行通信
3.3.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
| #ifndef MAINWINDOW_H #define MAINWINDOW_H
#include <QMainWindow> #include <QTcpSocket> #include <QLabel>
QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE
class MainWindow : public QMainWindow { Q_OBJECT
public: MainWindow(QWidget *parent = nullptr); ~MainWindow();
private slots: void on_conBtn_clicked();
void on_disBtn_clicked();
void on_sendBtn_clicked();
private: Ui::MainWindow *ui; QTcpSocket* m_tcp; QLabel* m_status; }; #endif
|
源文件
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
| #include "mainwindow.h" #include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
setWindowTitle("Tcp - client"); m_tcp = new QTcpSocket(this);
connect(m_tcp,&QTcpSocket::readyRead,this,[=](){ QString recvmsg = m_tcp->readAll(); ui->record->append("server say : " + recvmsg); });
connect(m_tcp,&QTcpSocket::connected,this,[=](){ ui->record->append("connect sucessfully"); m_status->setPixmap(QPixmap(":/bingo").scaled(20,20)); });
connect(m_tcp,&QTcpSocket::disconnected,this,[=](){ ui->record->append("disconnect......"); ui->disBtn->setEnabled(false); ui->conBtn->setEnabled(true); m_status->setPixmap(QPixmap(":/wrong").scaled(20,20)); });
m_status = new QLabel; m_status->setPixmap(QPixmap(":/wrong").scaled(20,20)); ui->statusbar->addWidget(new QLabel("connect status : ")); ui->statusbar->addWidget(m_status); }
MainWindow::~MainWindow() { delete ui; }
void MainWindow::on_conBtn_clicked() { QString ip = ui->Ip->text(); unsigned short port_tmp = ui->port->text().toInt();
m_tcp->connectToHost(QHostAddress(ip),port_tmp); ui->conBtn->setEnabled(false); ui->disBtn->setEnabled(true); }
void MainWindow::on_disBtn_clicked() { m_tcp->close(); ui->disBtn->setEnabled(false); ui->conBtn->setEnabled(true); }
void MainWindow::on_sendBtn_clicked() { QString sendmsg = ui->msg->toPlainText(); m_tcp->write(sendmsg.toUtf8()); ui->record->append("client say : " + sendmsg); ui->msg->clear(); }
|