QT开发--多线程

第十四章 多线程

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、 使用 QObjectmoveToThread():创建一个继承 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 &param) {    
        // 执行耗时或阻塞任务  
        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(); // 执行应用程序事件循环
}
当QSemaphore Semaphore(2)

当QSemaphore Semaphore(3)

(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();
}
这两种结果都是正常现象,因为没有控制线程的执行顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大象荒野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值