在 Qt 中,多线程的处理一般是通过 QThread类 来实现。
QThread 代表一个在应用程序中可以 独立控制 的线程,也可以和进程中的其他线程共享数据QThread 对象管理程序中的一个控制线程。创建线程的两种方式
① 使用 QThread 类 QThread 类是 Qt 中实现多线程的基础类之一,通过继承 QThread 类并重写其 run() 函数可以实现自定义线程逻辑。 线程类
#ifndef WORKER_H
#define WORKER_H
#include <QThread>
class Worker : public QThread
{
public:
Worker();
void run();
void printFunc();
};
#endif // WORKER_H实现如下:
#include "Worker.h"
#include <QDebug>
Worker::Worker()
{
}
void Worker::run()
{
qDebug()<<"子线程ThreadID: "<<QThread::currentThreadId();
}
void Worker::printFunc()
{
qDebug()<<"子线程成员函数ThreadID: "<<QThread::currentThreadId();
}main 函数
#include <iostream>
#include <QDebug>
#include "Worker.h"
using namespace std;
int main()
{
Worker w;
w.start();
qDebug()<<"主线程ThreadID: "<<QThread::currentThreadId();
w.printFunc();
while (1)
{
}
return 0;
}结果分析:
② 使用 moveToThread()
moveToThread() 是 Qt 中用于将对象移动到另一个线程的方法。通过调用 moveToThread() 函数,可以将一个 QObject 对象从当前线程移动到另一个线程中,从而实现对象在新线程中执行特定的任务。
在多线程编程中,通常会使用 moveToThread() 方法来将耗时的任务或需要在单独线程中执行的逻辑移动到单独的线程中,以避免阻塞主线程(通常是 GUI 线程)的执行。线程类:
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
Worker();
void printFunc();
public slots:
void doWork();
void doWork2();
void doWork3();
signals:
void testdoWork3();
};
#endif // WORKER_H实现如下:
#include "Worker.h"
#include <QDebug>
#include <QThread>
Worker::Worker()
{
}
void Worker::printFunc()
{
qDebug() << "成员函数ThreadID:"<<QThread::currentThreadId();
}
void Worker::doWork()
{
qDebug() << "doWork ThreadID:"<<QThread::currentThreadId();
}
void Worker::doWork2()
{
qDebug() << "doWork2 ThreadID:"<<QThread::currentThreadId();
}
void Worker::doWork3()
{
qDebug() << "doWork3 ThreadID:"<<QThread::currentThreadId();
}main 函数
#include "mainwindow.h"
#include <QApplication>
#include "Worker.h"
#include <QDebug>
#include <QThread>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
//MainWindow w;
//w.show();
Worker worker;
QThread thread;
worker.moveToThread(&thread);
QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork); //第一槽函数
QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork2); //第二槽函数
QObject::connect(&worker, &Worker::testdoWork3, &worker, &Worker::doWork3); //第三槽函数
//启动线程
thread.start();
//调用成员数
worker.printFunc();
//发送自定义信号
emit worker.testdoWork3();
while (1) {
}
return a.exec();
}结果分析:
对比 QThread 和 moveToThread()
① QThread 方式:
使用场景:
优点:
缺点:
② moveToThread() 方式:
使用场景:
优点:
缺点:
总结: 选择使用 QThread 或 moveToThread() 方式创建线程取决于具体需求和情况。可以根据以下原则进行选择:
综上所述,根据项目需求、任务复杂度和开发方便性来选择适合的创建线程方式
方法名 | 作用 |
|---|---|
run() | 线程入口函数 |
start() | 通过调用 run() 开始执行线程,操作系统会根据 优先级参数调度 线程。如果线程正在运行,则这个函数什么都不会做 |
currentThread() | 返回一个指向管理当前执行线程的 QThread 的指针 |
isRunning() | 如果线程正在运行返回 true,否则反之 |
sleep() / msleep() / usleep() | 使线程休眠,单位为 秒/毫秒/微秒 |
wait() | 阻塞线程,直到满足以下任何一个条件: 与此 QThread 对象关联的线程已经完成执行(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。 已经过了几毫秒。如果时间是 ULONG MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回false。这提供了与 POSIX pthread_join()函数类似的功能。 |
terminate() | 终止线程执行。(可以选择立即终止,也可以不立即终止)取决于操作系统的 调度策略,在 terminate() 之后使用 QThread::wait() 来确保 |
finished() | 当线程结束时会发出该信号,通过其来实现线程清理工作 |
在使用QThread类中的常用函数时,有一些注意事项需要注意:
start() 函数:调用start()函数启动线程时,会自动调用线程对象的run()方法。不要直接调用run()方法来启动线程,应该使用start()函数。wait() 函数:wait()函数会阻塞当前线程,直到线程执行完成。在调用wait()函数时需要确保不会发生死锁的情况,避免主线程和子线程相互等待对方执行完成而无法继续。terminate() 函数:调用terminate()函数会强制终止线程,这样可能会导致资源未能正确释放,造成内存泄漏等问题。因此应该尽量避免使用terminate()函数,而是通过设置标志让线程自行退出。quit() 函数:quit()函数用于终止线程的事件循环,通常与exec()函数一起使用。在需要结束线程事件循环时,可以调用quit()函数。finished 信号:当线程执行完成时会发出finished信号,可以连接这个信号来处理线程执行完成后的操作。yieldCurrentThread() 函数:yieldCurrentThread()函数用于让当前线程让出时间片,让其他线程有机会执行。使用时应该注意避免过多的调用,否则会影响程序性能。【实现倒计时页面】
1、创建项目,以 Widget 作为基类,设计 UI 界面如下:

2、新建一个类,并且继承于 QThread 类

3、thread.h 声明实现如下:

run 方法 在 thread.cpp 实现如下:
void Thread::run()
{
// 这里我们不能直接去修改界面内容
// 原因:存在线程安全问题,Qt 针对界面的控件状态的任何修改必须在 主函数 中进行
// 这里我们就仅仅针对时间进行计时即可
// 每隔一秒 通知主线程更新界面内容
for(int i = 0; i < 10; i++){
sleep(1);
// 发送一个信号 通知主线程
emit notify();
}
}4、Widget 声明实现如下:

Widget.cpp 代码如下:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 连接信号槽,通过槽函数更新界面
connect(&thread, &Thread::notify, this, &Widget::handle);
// 启动线程
thread.start();
}
void Widget::handle()
{
// 此处修改界面内容
int val = ui->lcdNumber->intValue();
val--;
ui->lcdNumber->display(val);
}💡 说明:
connect()函数第五个参数为 Qt::ConnectionType,用于指定信号和槽的连接类型。同时影响信号的传递方式和槽函数的执行顺序。
Qt::ConnectionType 提供了以下五种方式:
名称 | 作用 |
|---|---|
Qt::AutoConnection | 在 Qt中,会根据信号和槽函数所在的线程 自动选择 连接类型。如果信号和槽函数在同一线程中,那么使用 Qt:DirectConnection 类型;如果它们位于不同的线程中,那么使用Ot::QueuedConnection 类型 |
Qt::DirectConnection | 当信号发出时,槽函数会 立即在同一线程中执行 。这种连接类型适用于信号和槽函数在同一线程中的情况,可以实现直接的函数调用,但需要注意线程安全性 |
Qt::QueuedConnection | 当信号发出时,槽函数会被插入到接收对象所属的线程的事件队列中,等待下一次事件循环时执行。这种连接类型适用于信号和槽函数在不同线程中的情况,可以确保线程安全 |
Qt::BlockingQueuedConnection | 与 Qt:QueuedConnection 类似,但是发送信号的线程会被 阻塞,直到函数执行完毕,这种连接类型适用于需要等待槽函数执行完毕再继续的场景,但需要注意可能引起 线程死锁 的风险。 |
Qt::UniqueConnection | 这是一个标志,可以使用 位或 与上述任何一种连接类型组合使用 |
实现线程互斥和同步常用的类有:
互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在Qt中,互斥锁主要是通过 QMutex 类来处理。
QMutex 是Qt框架提供的互斥锁类,用于 保护共享资源 的访问,实现线程间的互斥操作。QMutex mutex;
mutex.lock(); //上锁
//访问共享资源
//...
mutex.unlock(); //解锁【案例:多线程自加】
先创建一个项目, 以 QWidget 作为基类,然后再创建C++ Class文件 Thread(新建一个类,并且继承于 QThread 类),和线程使用那步骤类似,如下:
声明如下:

run 方法 在 Thread.cpp 实现如下:
// 头文件既要声明,.cpp 文件也要定义
int Thread::num = 0;
QMutex Thread::mutex;
void Thread::run()
{
for(int i = 0; i < 50000; i++){
mutex.lock();
num++;
mutex.unlock();
}
}Widget.cpp 构造函数如下:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建两个线程对象
Thread t1, t2;
t1.start();
t2.start();
// 加上两个线程,让主线程等待两线程结束
t1.wait();
t2.wait();
// 打印结果
qDebug() << Thread::num;
}最后结果不出意外:100000,如果我们不加锁的话,就会出现意外,可以自行测试
QMutexLocker 是 QMutex 的辅助类,使用 RAII(Resource Acquisition ls Initialization) 方式对互斥锁进行 上锁和解锁 操作。C++ 中引入 智能指针 就是解决这个问题的,C++ 11 引入了 std::lock_guard 相当于是 std::mutex 智能指针,借助了 RAII 机制
QMutex mutex;
{
QMutexLocker locker(&mutex)://在作用域内自动上锁
// 访间共享资源
// ...
}//在作用城结束时自动解锁如下,借助上面互斥锁的例子,做点修改

特点:
QReadWriteLock 是读写锁类,用于控制读和写的并发访问。QReadLocker 用于读操作上锁,允许多个线程同时读取共享资源。QWriteLocker 用于写操作上锁,只允许一个线程写入共享资源。**用途:**在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式。
QReadWriteLock rwLock;
//在读操作中使用读锁
{
QReadLocker locker(&rwLock); //在作用域内自动上读锁
//读取共享资源
//...
} //在作用域结束时自动解读锁
//在写操作中使用写锁
{
QWriteLocker locker(&rwLock); //在作用用域内自动上写锁
//修改共享资源
//...
} //在作用域结束时自动解写锁在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还 必须等待某些条件满足 才能执行,这时就会出现问题。
在 Qt 中,专门提供了 QWaitCondition类 来解决像上述这样的问题。
特点:QWaitCondition 是Qt框架提供的条件变量类,用于线程之间的消息通信和同步。 用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。
QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满足,若不满足则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满足并释放锁
}
//条件满足后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();有时在多线程编程中,需要确保多个线程可以相应的 访问一个数量有限的相同资源
例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用 信号量 来处理。
信号量类似于 增强 的互斥锁(Plus 版),不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量。
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作代码示例如下:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QSemaphore>
QSemaphore semaphore(2); // 定义能够同时访问资源的线程数量为2的信号量
class MyThread : public QThread // 定义一个线程类
{
public:
void run() override {
if(semaphore.tryAcquire()) { // 尝试获取信号量
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Acquired Semaphore"; // 输出线程ID和已获取信号量消息
sleep(2); // 线程休眠2秒
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Releasing Semaphore"; // 输出线程ID和释放信号量消息
semaphore.release(); // 释放信号量
} else {
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Semaphore not acquired"; // 输出线程ID和未获取信号量消息
}
}
};
int main(int argc, char *argv[]) // 主函数
{
QCoreApplication a(argc, argv); // 创建应用程序对象
MyThread thread1; // 创建线程对象1
MyThread thread2; // 创建线程对象2
MyThread thread3; // 创建线程对象3
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
thread3.start(); // 启动线程3
thread1.wait(); // 等待线程1结束
thread2.wait(); // 等待线程2结束
thread3.wait(); // 等待线程3结束
return a.exec(); // 执行应用程序事件循环
}