第十四章 多线程
QT实现多线程有 4 种方法,分别是
1、继承QThread类重写run()方法。
2、继承QObject类,将其moveToThread()到QThread对象。
3、继承QRunnable类,重写run()方法,使用QThreadPool启动线程。
4、使用静态函数QtConcurrent::run()将任务函数运行在子线程中。
14.0 QThread类和QObject类
QThread 是 Qt 中实现多线程编程的核心类,提供跨平台线程管理。
使用 QThread 有两种方法:
1、 继承 QThread:重写 run() 方法,实现线程的具体操作。Qt4.8 之前较常用。
2、 使用 QObject 和 moveToThread():创建一个继承 QObject 的工作对象,使用 moveToThread() 将其移动到一个 QThread 对象中运行。官方推荐。
//当你调用moveToThread()时,
//你指定的QObject及其所有子对象将会在与该QThread对象相关联的线程中执行它们的槽函数。
void QObject::moveToThread(QThread *thread);
start(): 启动线程,使线程进入运行状态,调用线程的run()方法。
run(): 线程的执行函数,需要在该函数中编写线程所需执行的任务。
quit(): 终止线程的事件循环,在下一个事件处理周期结束时退出线程。
wait(): 阻塞当前线程,直到线程执行完成或超时。
finished(): 在线程执行完成时发出信号。
terminate(): 强制终止线程的执行,不推荐使用,可能导致资源泄漏和未定义行为。
isRunning(): 判断线程是否正在运行。
currentThreadId(): 返回当前线程的ID。
yieldCurrentThread(): 释放当前线程的时间片,允许其他线程执行。
setPriority(): 设置线程优先级。
msleep(): 让当前线程休眠指定的毫秒数。
在使用QThread类中的常用函数时,有一些注意事项需要注意:
start()函数:
调用start()函数启动线程时,会自动调用线程对象的run()方法。不要直接调用run()方法来启动线程,应该使用start()函数。
wait()函数:
wait()函数会阻塞当前线程,直到线程执行完成。在调用wait()函数时需要确保不会发生死锁的情况,避免主线程和子线程相互等待对方执行完成而无法继续。
terminate()函数:
调用terminate()函数会强制终止线程,这样可能会导致资源未能正确释放,造成内存泄漏等问题。因此应该尽量避免使用terminate()函数,而是通过设置标志让线程自行退出。
quit()函数:
quit()函数用于终止线程的事件循环,通常与exec()函数一起使用。在需要结束线程事件循环时,可以调用quit()函数。
finished信号: 当线程执行完成时会发出finished信号,可以连接这个信号来处理线程执行完成后的操作。
yieldCurrentThread()函数:
yieldCurrentThread()函数用于让当前线程让出时间片,让其他线程有机会执行。使用时应该注意避免过多的调用,否则会影响程序性能。
14.1 继承 QThread 的线程
继承QThread是创建线程的一种方法。
在这种方法中,只有重写的run()方法运行在子线程中,其他在类内定义的方法仍然在主线程中执行。用户需重写run()方法,并将耗时操作置于其中。
用 connect关联 QThread类的对象的信号和 QWidget的槽函数。
主线程和子线程执行的顺序不确定,偶尔主线程在前,偶尔子线程在前。
子线程类的成员函数包括槽函数是运行在主线程当中的,只有run()函数运行在子线程中。
如果在run()函数中调用子线程类成员函数,那么该成员函数运行在子线程中。
14.1.1 应用实例
// 告诉线程启动
QThread::start();
// 告诉线程退出
QThread::quit();
// 阻塞等待线程真正结束
QThread::wait();
本例展示了如何通过继承QThread类来创建和使用线程。在MainWindow类中,我们使用了一个按钮来启动线程,并在线程完成后接收其发送的信号。
class WorkerThread : public QThread {
Q_OBJECT
public:
void run() override {
QString result;
// 执行耗时或阻塞任务,并将结果存储在result中
emit resultReady(result); // 任务完成后,发出包含结果的信号
}
signals:
void resultReady(const QString &s); // 定义信号,用于传递任务结果
};
// 使用部分
WorkerThread *worker = new WorkerThread; // 创建WorkerThread对象
// 连接信号到处理结果的槽函数
connect(worker, &WorkerThread::resultReady, this, &MyObject::handleResults);
worker->start(); // 启动工作线程
按钮点击后,QThread类的start()函数开始运行,run()函数执行,
2s后发送 emit resultReady(result);信号,触发槽函数打印结果。
14.2 继承 QObject 的线程
继承QObject类并使用 QObject::moveToThread()方法。
继承QObject类的方法更为灵活,因为它允许将一个QObject对象转移到另一个线程中执行。
槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里运行。
成员函数和主函数运行在主线程当中。
14.2.1 应用实例
mainWindow窗口类有 Worker:QObject成员,QThread成员。
Worker中定义任务,使用 QObject::moveToThread(&qthread)来代替重写QThread的run方法,使得线程可以执行这个任务。
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork(const QString ¶m) {
// 执行耗时或阻塞任务
emit resultReady(result); // 任务完成后发出结果信号
}
signals:
void resultReady(const QString &result); // 定义结果信号
};
// 使用部分
QThread workerThread; // 创建工作线程
Worker *worker = new Worker; // 创建工作对象
worker->moveToThread(&workerThread); // 将工作对象移动到工作线程
// 线程结束时删除工作对象
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
// 连接操作信号到工作槽
connect(this, &Controller::operate, worker, &Worker::doWork);
workerThread.start(); // 启动工作线程

14.3 继承QRunnable类
实现方法:
- 继承QRunnable类,重写run函数,使用QThreadPool启动线程。
class Runnable : public QRunnable {
public:
void run() override {
// 执行耗时或阻塞任务
}
};
// 使用部分
Runnable runObj; // 创建Runnable对象,定义要执行的任务
// 获取全局线程池实例
QThreadPool *threadPool = QThreadPool::globalInstance();
// 将任务提交给线程池执行
threadPool->start(&runObj);
特点:
- 优点:无需手动管理线程资源,QThreadPool自动释放。
- 缺点:无法使用信号槽通信。
- 适用场景:线程任务量大,需频繁创建线程。
14.3.x QT实现线程池
QThreadPool类,是QT5.4及之后版本提供的一个功能。
QThreadPool可以与QRunnable接口一起使用,以简化在线程池中执行任务的过程。
创建一个类继承自QRunnable,并重写run()方法以实现你的任务逻辑。
使用QThreadPool::globalInstance()->start(runnable)将任务提交到全局线程池。
管理线程池(可选):
如果你需要更细粒度的控制,可以创建自己的QThreadPool实例,并使用它来管理线程。
14.4 QtConcurrent的静态run方法
实现方法:
- 使用QtConcurrent::run将任务函数运行在子线程中。
void func(QString name) {
qDebug() << name << "from" << QThread::currentThread();
}
// 使用
QFuture<void> future = QtConcurrent::run(func, QString("Thread Task"));
//阻塞,无返回值
future.waitForFinished();
特点:
- 优点:使用简单,无需继承任何类,自动根据CPU核数调整线程数。
- 缺点:无法直接使用信号槽通信,需手动等待线程结束。
- 适用场景:简单并行任务,需快速将任务丢入子线程执行。
14.5 线程的同步与互斥
(1)QMutex / QMutexLocker
QMutex是Qt中的互斥锁类,用于多线程程序中的共享资源互斥访问,提供加锁和解锁操作。QMutexLocker是QMutex的RAII风格封装,自动管理锁获取和释放,减少因忘解锁导致死锁风险。
QMutexLocker 在创建时会自动调用 QMutex 的 lock() 方法,析构时会自动调用 QMutex 的 unlock() 方法。因此使用 QMutexLocker 可以大大减少忘记解锁的情况。
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
// 定义共享资源
int sharedValue = 0;
QMutex mutex;
// 定义一个线程类
class MyThread : public QThread
{
public:
void run() override {
for(int i = 0; i < 5; i++) {
mutex.lock(); // 加锁
sharedValue++; // 访问共享资源
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
msleep(1000); // 线程休眠1秒
mutex.unlock(); // 解锁
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
qDebug() << "Final Shared Value: " << sharedValue;
return a.exec();
}
明显看出在未加锁情况下对临界资源的访问出现混乱的结果。
使用QMutexLocker会更简洁,原理与QMutex一致。代码如下
void run() override {
for(int i = 0; i < 5; i++) {
QMutexLocker locker(&mutex); // 创建 QMutexLocker 对象并传递 QMutex 对象
sharedValue++; // 访问共享资源
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
msleep(1000); // 线程休眠1秒
}
}
(2)QSemaphore
QSemaphore是Qt中的信号量类,用于控制共享资源的访问数量,实现线程同步与协调。信号量正时线程可访问,为零时线程阻塞,直至信号量被释放。
#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(); // 执行应用程序事件循环
}
(3)QWaitCondition
QWaitCondition是Qt中用于线程同步的类,与QMutex配合使用。
QMutex保护资源,QWaitCondition挂起线程等待条件,条件满足时唤醒线程。
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>
QMutex mutex; // 创建一个互斥锁,确保线程安全
QWaitCondition queueNotEmpty; // 创建一个条件变量,表示队列非空
QQueue<int> queue; // 创建一个队列用于存储数据
// 生产者线程
class ProducerThread : public QThread
{
public:
void run() override
{
for (int i = 0; i < 10; ++i) {
// 生产数据并加入队列
{
QMutexLocker locker(&mutex); // 加锁
queue.enqueue(i); // 生产数据并加入队列
qDebug() << "Produced: " << i;
queueNotEmpty.wakeOne(); // 通知消费者队列非空
}
msleep(100); // 休眠一段时间
}
}
};
// 消费者线程
class ConsumerThread : public QThread
{
public:
void run() override
{
for (int i = 0; i < 10; ++i) {
// 检查队列是否为空,如果为空则等待
{
QMutexLocker locker(&mutex); // 加锁
while (queue.isEmpty()) {
queueNotEmpty.wait(&mutex); // 等待条件变量,直到队列非空
}
int value = queue.dequeue(); // 从队列中取出数据
qDebug() << "Consumed: " << value;
}
msleep(200); // 休眠一段时间
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建生产者线程和消费者线程
ProducerThread producer;
ConsumerThread consumer;
// 启动线程
producer.start();
consumer.start();
// 等待线程结束
producer.wait();
consumer.wait();
return a.exec();
}
(4)QReadWriteLock
QReadWriteLock 是 Qt 提供的用于读写操作的锁类,允许多个线程同时读取共享数据,但在写操作时会阻止其他的读取和写入,以确保数据的一致性。
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QReadWriteLock>
QString sharedData; // 共享数据变量
QReadWriteLock rwLock; // 读写锁
// 读取操作线程
class ReaderThread : public QThread
{
public:
void run() override
{
rwLock.lockForRead(); // 以读取方式加锁
qDebug() << "Read Data: " << sharedData; // 输出读取的数据
rwLock.unlock(); // 释放锁
}
};
// 写入操作线程
class WriterThread : public QThread
{
public:
void run() override
{
rwLock.lockForWrite(); // 以写入方式加锁
sharedData = "Hello, world!"; // 写入数据
qDebug() << "Write Data: " << sharedData; // 输出写入的数据
rwLock.unlock(); // 释放锁
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建读取线程和写入线程
ReaderThread reader;
WriterThread writer;
// 启动线程
reader.start(); // 启动读取线程
writer.start(); // 启动写入线程
// 等待线程结束
reader.wait(); // 等待读取线程结束
writer.wait(); // 等待写入线程结束
return a.exec();
}
1479

被折叠的 条评论
为什么被折叠?



