C++ Qt开发:运用QThread多线程组件
Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QThread组件实现多线程功能。
多线程技术在程序开发中尤为常用,Qt框架中提供了QThread库来实现多线程功能。当你需要使用QThread时,需包含QThread模块,以下是QThread类的一些主要成员函数和槽函数。
| 成员函数/槽函数 | 描述 |
|---|---|
QThread(QObject *parent = nullptr) |
构造函数,创建一个QThread对象。 |
~QThread() |
析构函数,释放QThread对象。 |
void start(QThread::Priority priority = InheritPriority) |
启动线程。 |
void run() |
默认的线程执行函数,需要在继承QThread的子类中重新实现以定义线程的操作。 |
void exit(int returnCode = 0) |
请求线程退出,线程将在适当的时候退出。 |
void quit() |
请求线程退出,与exit()类似。 |
void terminate() |
立即终止线程的执行。这是一个危险的操作,可能导致资源泄漏和未完成的操作。 |
void wait() |
等待线程完成。主线程将被阻塞,直到该线程退出。 |
bool isRunning() const |
检查线程是否正在运行。 |
void setPriority(Priority priority) |
设置线程的优先级。 |
Priority priority() const |
获取线程的优先级。 |
QThread::Priority priority() |
获取线程的优先级。 |
void setStackSize(uint stackSize) |
设置线程的堆栈大小(以字节为单位)。 |
uint stackSize() const |
获取线程的堆栈大小。 |
void msleep(unsigned long msecs) |
使线程休眠指定的毫秒数。 |
void sleep(unsigned long secs) |
使线程休眠指定的秒数。 |
static QThread *currentThread() |
获取当前正在执行的线程的QThread对象。 |
void setObjectName(const QString &name) |
为线程设置一个对象名。 |
当我们需要创建线程时,通常第一步则是要继承QThread类,并重写类内的run()方法,在run()方法中,你可以编写需要在新线程中执行的代码。当你创建一个QThread的实例并调用它的start()方法时,会自动调用run()来执行线程逻辑,如下这样一段代码展示了如何运用线程类。
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
class MyThread : public QThread
{
public:
void run() override
{
for (int i = 0; i < 5; ++i)
{
qDebug() << "Thread is running" << i;
sleep(1);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread;
thread.start();
thread.wait();
qDebug() << "Main thread is done.";
return a.exec();
}
上述代码运行后则会每隔1秒输出一段话,在主函数内通过调用thread.start方法启动这个线程,并通过thread.wait等待线程结束,如下图所示;

1.1 线程组与多线程
线程组是一种组织和管理多个线程的机制,允许将相关联的线程集中在一起,便于集中管理、协调和监控。通过线程组,可以对一组线程进行统一的生命周期管理,包括启动、停止、调度和资源分配等操作。
上述方法并未真正实现多线程功能,我们继续完善MyThread自定义类,在该类内增加两个标志,is_run()用于判断线程是否正在运行,is_finish()则用来判断线程是否已经完成,并在run()中增加打印当前线程对象名称的功能。
class MyThread: public QThread
{
protected:
volatile bool m_to_stop;
protected:
void run()
{
for(int x=0; !m_to_stop && (x <10); x++)
{
msleep(1000);
std::cout << objectName().toStdString() << std::endl;
}
}
public:
MyThread()
{
m_to_stop = false;
}
void stop()
{
m_to_stop = true;
}
void is_run()
{
std::cout << "Thread Running = " << isRunning() << std::endl;
}
void is_finish()
{
std::cout << "Thread Finished = " << isFinished() << std::endl;
}
};
接着在主函数内调整,增加一个MyThread thread[10]用于存储线程组,线程组是一种用于组织和管理多个线程的概念。在不同的编程框架和操作系统中,线程组可能具有不同的实现和功能,但通常用于提供一种集中管理和协调一组相关线程的机制。
我们通过循环的方式依次对线程组进行赋值,通过调用setObjectName对每一个线程赋予一个不同的名称,当需要使用这些线程时则可以通过循环调用run()方法来实现,而结束调用同样如此,如下是调用的具体实现;
#include <QCoreApplication>
#include <iostream>
#include <QThread>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 定义线程数组
MyThread thread[10];
// 设置线程对象名字
for(int x=0;x<10;x++)
{
thread[x].setObjectName(QString("thread => %1").arg(x));
}
// 批量调用run执行
for(int x=0;x<10;x++)
{
thread[x].start();
thread[x].is_run();
thread[x].isFinished();
}
// 批量调用stop关闭
for(int x=0;x<10;x++)
{
thread[x].wait();
thread[x].stop();
thread[x].is_run();
thread[x].is_finish();
}
return a.exec();
}
如下图则是运行后实现的多线程效果;

1.2 向线程中传递参数
向线程中传递参数是多线程编程中常见的需求,不同的编程语言和框架提供了多种方式来实现这个目标,在Qt中,由于使用的自定义线程类,所以可通过增加一个set_value()方法来向线程内传递参数,由于线程函数内的变量使用了protected属性,所以也就实现了线程间变量的隔离,当线程被执行结束后则可以通过result()方法获取到线程执行结果,这个线程函数如下所示;
class MyThread: public QThread
{
protected:
int m_begin;
int m_end;
int m_result;
void run()
{
m_result = m_begin + m_end;
}
public:
MyThread()
{
m_begin = 0;
m_end = 0;
m_result = 0;
}
// 设置参数给当前线程
void set_value(int x,int y)
{
m_begin = x;
m_end = y;
}
// 获取当前线程名
void get_object_name()
{
std::cout << "this thread name => " << objectName().toStdString() << std::endl;
}
// 获取线程返回结果
int result()
{
return m_result;
}
};
在主函数中,我们通过MyThread thread[3];来定义3个线程组,并通过循环三次分别thread[x].set_value()设置三组不同的参数,当设置完成后则可以调用thread[x].start()方法运行这些线程,线程运行结束后则返回值将会被依次保存在thread[x].result()中,此时直接将其相加即可得到最终线程执行结果;
#include <QCoreApplication>
#include <iostream>
#include <QThread>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread[3];
// 分别将不同的参数传入到线程函数内
for(int x=0; x<3; x++)
{
thread[x].set_value(1,2);
thread[x].setObjectName(QString("thread -> %1").arg(x));
thread[x].start();
}
// 等待所有线程执行结束
for(int x=0; x<3; x++)
{
thread[x].get_object_name();
thread[x].wait();
}
// 获取线程返回值并相加
int result = thread[0].result() + thread[1].result() + thread[2].result();
std::cout << "sum => " << result << std::endl;
return a.exec();
}
程序运行后,则可以输出三个线程相加的和;

1.3 互斥同步线程锁
QMutex 是Qt框架中提供的用于线程同步的类,用于实现互斥访问共享资源。Mutex是“互斥锁(Mutual Exclusion)”的缩写,它能够确保在任意时刻,只有一个线程可以访问被保护的资源,从而避免了多线程环境下的数据竞争和不一致性。
在Qt中,QMutex提供了简单而有效的线程同步机制,其基本用法包括:
- 锁定(Lock): 线程在访问共享资源之前,首先需要获取
QMutex的锁,这通过调用lock()方法来实现。 - 解锁(Unlock): 当线程使用完共享资源后,需要释放
QMutex的锁,以允许其他线程访问,这通过调用unlock()方法来实现。
该锁lock()锁定与unlock()解锁必须配对使用,线程锁保证线程间的互斥,利用线程锁能够保证临界资源的安全性。
- 线程锁解决的问题: 多个线程同时操作同一个全局变量,为了防止资源的无序覆盖现象,从而需要增加锁,来实现多线程抢占资源时可以有序执行。
- 临界资源(Critical Resource): 每次只允许一个线程进行访问 (读/写)的资源。
- 线程间的互斥(竞争): 多个线程在同一时刻都需要访问临界资源。
- 一般性原则: 每一个临界资源都需要一个线程锁进行保护。
我们以生产者消费者模型为例来演示锁的使用方法,生产者消费者模型是一种并发编程中常见的同步机制,用于解决多线程环境下的协作问题。该模型基于两类角色:生产者(Producer)和消费者(Consumer),它们通过共享的缓冲区进行协作。
主要特点和工作原理如下:
- 生产者:
- 生产者负责产生一些资源或数据,并将其放入共享的缓冲区中。生产者在生产资源后,需要通知消费者,以便它们可以取走资源。
- 消费者:
- 消费者从共享的缓冲区中取走资源,并进行相应的处理。如果缓冲区为空,消费者需要等待,直到有新的资源可用。
- 共享缓冲区:
- 作为生产者和消费者之间的交换介质,共享缓冲区存储被生产者产生的资源。它需要提供对资源的安全访问,以防止竞态条件和数据不一致性。
- 同步机制:
- 生产者和消费者之间需要一些同步机制,以确保在正确的时机进行资源的生产和消费。典型的同步机制包括信号量、互斥锁、条件变量等。
生产者消费者模型的典型应用场景包括异步任务处理、事件驱动系统、数据缓存等。这种模型的实现可以通过多线程编程或使用消息队列等方式来完成。
首先在全局中引入#include <QMutex>库,并在全局定义static QMutex线程锁变量,接着我们分别定义两个自定义线程函数,其中Producer代表生产者,而Customer则是消费者,生产者中负责每次产出一个随机数并将其追加到g_store全局变量内保存,消费者则通过g_store.remove每次取出一个元素。
static QMutex g_mutex; // 线程锁
static QString g_store; // 定义全局变量
class Producer : public QThread
{
protected:
void run()
{
int count = 0;
while(true)
{
// 加锁
g_mutex.lock();
g_store.append(QString::number((count++) % 10));
std::cout << "Producer -> "<< g_store.toStdString() << std::endl;
// 释放锁
g_mutex.unlock();
msleep(900);
}
}
};
class Customer : public QThread
{
protected:
void run()
{
while( true )
{
g_mutex.lock();
if( g_store != "" )
{
g_store.remove(0, 1);
std::cout << "Curstomer -> "<< g_store.toStdString() << std::endl;
}
g_mutex.unlock();
msleep(1000);
}
}
};
在主函数中分别定义两个线程类,并依次运行它们;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer p;
Customer c;
p.setObjectName("producer");
c.setObjectName("curstomer");
p.start();
c.start();
return a.exec();
}
至此,生产者产生数据,消费者消费数据;如下图所示;

QMutexLocker 是Qt框架中提供的一个辅助类,它是在QMutex基础上简化版的线程锁,QMutexLocker会保护加锁区域,并自动实现互斥量的锁定和解锁操作,可以将其理解为是智能版的QMutex锁,通过 QMutexLocker可以确保在作用域内始终持有锁,从而避免因为忘记释放锁而导致的问题。该锁只需要在上方代码中稍加修改即可。
使用 QMutexLocker 的一般流程如下:
- 创建一个
QMutex对象。 - 创建一个
QMutexLocker对象,传入需要锁定的QMutex。 - 在
QMutexLocker对象的作用域内进行需要互斥访问的操作。 - 当
QMutexLocker对象超出作用域范围时,会自动释放锁。
static QMutex g_mutex; // 线程锁
static QString g_store; // 定义全局变量
class Producer : public QThread
{
protected:
void run()
{
int count = 0;
while(true)
{
// 增加智能线程锁
QMutexLocker Locker(&g_mutex);
g_store.append(QString::number((count++) % 10));
std::cout << "Producer -> "<< g_store.toStdString() << std::endl;
msleep(900);
}
}
};
1.4 读写同步线程锁
QReadWriteLock 是Qt框架中提供的用于实现读写锁的类。读写锁允许多个线程同时读取共享数据,但在写入数据时会互斥,确保数据的一致性和完整性。这对于大多数情况下读取频繁而写入较少的共享数据非常有用,可以提高程序的性能。
其提供了两种锁定操作:
- 读取锁(Read Lock): 允许多个线程同时获取读取锁,用于并行读取共享数据。在没有写入锁的情况下,多个线程可以同时持有读取锁。
- 写入锁(Write Lock): 写入锁是互斥的,当一个线程获取写入锁时,其他线程无法获取读取锁或写入锁。这确保了在写入数据时,不会有其他线程同时读取或写入。
互斥锁存在一个问题,每次只能有一个线程获得互斥量的权限,如果在程序中有多个线程来同时读取某个变量,那么使用互斥量必须排队,效率上会大打折扣,基于QReadWriteLock读写模式进行代码段锁定,即可解决互斥锁存在的问题。
#include <QCoreApplication>
#include <iostream>
#include <QThread>
#include <QMutex>
#include <QReadWriteLock>
static QReadWriteLock g_mutex; // 线程锁
static QString g_store; // 定义全局变量
class Producer : public QThread
{
protected:
void run()
{
int count = 0;
while(true)
{
// 以写入方式锁定资源
g_mutex.lockForWrite();
g_store.append(QString::number((count++) % 10));
// 写入后解锁资源
g_mutex.unlock();
msleep(900);
}
}
};
class Customer : public QThread
{
protected:
void run()
{
while( true )
{
// 以读取方式写入资源
g_mutex.lockForRead();
if( g_store != "" )
{
std::cout << "Curstomer -> "<< g_store.toStdString() << std::endl;
}
// 读取到后解锁资源
g_mutex.unlock();
msleep(1000);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer p1,p2;
Customer c1,c2;
p1.setObjectName("producer 1");
p2.setObjectName("producer 2");
c1.setObjectName("curstomer 1");
c2.setObjectName("curstomer 2");
p1.start();
p2.start();
c1.start();
c2.start();
return a.exec();
}
该锁允许用户以同步读lockForRead()或同步写lockForWrite()两种方式实现保护资源,但只要有一个线程在以写的方式操作资源,其他线程也会等待写入操作结束后才可继续读资源。
1.5 基于信号线程锁
QSemaphore 是Qt框架中提供的用于实现信号量的类。信号量是一种用于在线程之间进行同步和通信的机制,它允许多个线程在某个共享资源上进行协调,控制对该资源的访问。QSemaphore 的主要作用是维护一个计数器,线程可以通过获取和释放信号量来改变计数器的值。
其主要方法包括:
QSemaphore(int n = 0):构造函数,创建一个初始计数值为n的信号量。void acquire(int n = 1):获取信号量,将计数器减去n。如果计数器不足,线程将阻塞等待。bool tryAcquire(int n = 1):尝试获取信号量,如果计数器足够,立即获取并返回true;否则返回false。void release(int n = 1):释放信号量,将计数器加上n。如果有等待的线程,其中一个将被唤醒。
信号量是特殊的线程锁,信号量允许N个线程同时访问临界资源,通过acquire()获取到指定资源,release()释放指定资源。
#include <QCoreApplication>
#include <iostream>
#include <QThread>
#include <QSemaphore>
const int SIZE = 5;
unsigned char g_buff[SIZE] = {0};
QSemaphore g_sem_free(SIZE); // 5个可生产资源
QSemaphore g_sem_used(0); // 0个可消费资源
// 生产者生产产品
class Producer : public QThread
{
protected:
void run()
{
while( true )
{
int value = qrand() % 256;
// 若无法获得可生产资源,阻塞在这里
g_sem_free.acquire();
for(int i=0; i<SIZE; i++)
{
if( !g_buff[i] )
{
g_buff[i] = value;
std::cout << objectName().toStdString() << " --> " << value << std::endl;
break;
}
}
// 可消费资源数+1
g_sem_used.release();
sleep(2);
}
}
};
// 消费者消费产品
class Customer : public QThread
{
protected:
void run()
{
while( true )
{
// 若无法获得可消费资源,阻塞在这里
g_sem_used.acquire();
for(int i=0; i<SIZE; i++)
{
if( g_buff[i] )
{
int value = g_buff[i];
g_buff[i] = 0;
std::cout << objectName().toStdString() << " --> " << value << std::endl;
break;
}
}
// 可生产资源数+1
g_sem_free.release();
sleep(1);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer p1;
Customer c1;
p1.setObjectName("producer");
c1.setObjectName("curstomer");
p1.start();
c1.start();
return a.exec();
}
C++ Qt开发:运用QThread多线程组件的更多相关文章
- 【Qt开发】QThread 实用技巧、误区----但文档中没有提到
本文主要内容: 在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个Label上.1. 用不正确的方式得到看似正确的结果2. 用Qt Manual 和 例子中使用的方法3. ...
- 【Qt开发】QThread介绍
回顾Qt之线程(QThread),里面讲解了如何使用线程,但还有很多人留言没有看明白,那么今天我们来一起瞅瞅关于QThread管理线程的那些事儿... 一.线程管理 1.线程启动 void start ...
- 【Qt开发】QThread中的互斥、读写锁、信号量、条件变量
在gemfield的<从pthread到QThread>一文中我们了解了线程的基本使用,但是有一大部分的内容当时说要放到这片文章里讨论,那就是线程的同步问题.关于这个问题,gemfield ...
- Qt 线程基础(QThread、QtConcurrent等)
[-] 使用线程 何时使用其他技术替代线程 应该使用 Qt 线程的哪种技术 Qt线程基础 QObject与线程 使用互斥量保护数据的完整 使用事件循环防止数据破坏 处理异步执行 昨晚看Qt的Manua ...
- Qt开发者关于QThread的咆哮——你们都用错了
Qt开发者关于QThread的咆哮——你们都用错了 我们(Qt用户)正广泛地使用IRC来进行交流.我在Freenode网站挂出了#qt标签,用于帮助大家解答问题.我经常看到的一个问题(这让我不厌其烦) ...
- Qt 线程基础(QThread、QtConcurrent等) 2
使用线程 基本上有种使用线程的场合: 通过利用处理器的多个核使处理速度更快. 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程. 何时使用其他技术替代线程 开发人员使用线 ...
- Qt 线程基础(QThread、QtConcurrent、QThreadPool等)
使用线程 基本上有种使用线程的场合: 通过利用处理器的多个核使处理速度更快. 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程. 何时使用其他技术替代线程 开发人员使 ...
- win使用MSYS2安装Qt开发环境
原文链接 MSYS2 下载地址: pacman的具体用法 有pacman的具体使用方法.我们首先对系统升级 我们首先对系统升级 pacman -Syu 就会检测整个系统可以升级的组件,并自动下载安装, ...
- QThread多线程编程经典案例分析
传统的图形界面应用程序都只有一个线程执行,并且一次执行一个操作.如果用户调用一个比较耗时的操作,就会冻结界面响应. 一个解决方法是按照事件处理的思路: 调用 Void QApplication::pr ...
- 基于QT开发的第三方库
基于Qt开发的第三方库 分类: Qt2014-02-12 11:34 1738人阅读 评论(0) 收藏 举报 QT第三方库 目录(?)[+] 文章来源:http://blog.csdn.net ...
随机推荐
- Rider调试时断点打不上(变灰色小叉)
记录我在使用rider调试Unity的C#代码时遇到断点变灰色小叉叉,断点打不上/(不会进入断点)的几种解决办法 首先要确保你没有禁用所有的断点,然后再尝试使用本文的三种方法. 不要禁用所有断点 在R ...
- 事务提交之后再执行某些操作 → 引发对 TransactionSynchronizationManager 的探究
开心一刻 昨晚,小妹跟我妈聊天 小妹:妈,跟你商量个事,我想换车,资助我点呀 妈:哎呀,你那分扣的攒一堆都够考清华的,还换车资助点,有车开就不错了 小妹:你要是这么逼我,别说哪天我去学人家傍大款啊 妈 ...
- 8.2 Windows驱动开发:内核解锁与强删文件
在某些时候我们的系统中会出现一些无法被正常删除的文件,如果想要强制删除则需要在驱动层面对其进行解锁后才可删掉,而所谓的解锁其实就是释放掉文件描述符(句柄表)占用,文件解锁的核心原理是通过调用ObSet ...
- 搭建私有仓库Registry(Docker Hub)
搭建私有仓库Registry(Docker Hub) 安装Docker 拉取仓库镜像:# docker pull registry 生成认证certificate mkdir ~/certs open ...
- element实现大图预览和图片动态加载
element的el-image组件支持大图预览模式,但需要和小图模式配合使用,项目中刚好有需求需要直接使用大图预览并且需要支持图片的动态加载,研究了一下el-image组件的源码发现el-image ...
- SQL布尔盲注
看不到回显时使用盲注 布尔盲注 在进行SQL注入时,web页面仅返回True和False 布尔盲注会根据web页面返回的True或者False信息,对数据库中的信息,对数据库中的信息进行猜解,并获取数 ...
- 多进程|基于非阻塞调用的轮询检测方案|进程等待|重新理解挂起|Linux OS
说在前面 今天给大家带来操作系统中进程等待的概念,我们学习的操作系统是Linux操作系统. 我们今天主要的目标就是认识wait和waitpid这两个系统调用. 前言 那么这里博主先安利一下一些干货满满 ...
- CF911G Mass Change Queries 题解
题目链接:CF 或者 洛谷 前置知识点:平衡树合并: CF文章 与维基百科 看上去这题有很多人用线段树分裂与合并去做,其实这种需要分裂和合并的,我们用文艺平衡树去维护区间信息是最容易写的. 考虑本题的 ...
- static_cast, dynamic_cast与reinterpret_cast的区别
在C++中,static_cast, dynamic_cast和reinterpret_cast都可用于类型转换,它们在具体使用时有什么区别?此外,更为重要的是,为什么不推荐使用强制类型转换? 1. ...
- 错误:tensorflow.python.framework.errors_impl.InvalidArgumentError: ValueError: attempt to get argmax of an empty sequence的解决方案
近日,在使用Cascade R-CNN完成目标检测任务时,我在使用这个模型训练自己的数据集时出现了如下错误: 具体如以下截图所示: 详细错误如下所示: Traceback (most recent c ...