Qt线程QThread详解

  • Post author:
  • Post category:其他




前言

在这里插入图片描述

在程序中使用线程可以提高程序的性能、并发性、响应性和稳定性,使得程序设计更加灵活和简单。但是,线程编程也有一些挑战,如线程安全性和死锁等问题需要格外注意。我们使用QT框架编程时,可以用框架提供的线程类QThread来管理线程。本文将介绍使用QThread编程时的两种方式,并给出一个数据同步的示例。



1.QThread介绍

在介绍QThread之前先复习下线程的定义,线程是操作系统中的基本执行单元,是一个独立的执行路径。每个线程都有自己的栈空间,用于存储本地变量和函数调用的上下文。多个线程可以在同一进程中并发执行,从而实现并发处理,提高程序的性能和响应能力。

与进程不同的是,线程是轻量级的,它们共享同一进程的地址空间,这意味着它们可以访问相同的内存和文件资源,从而更容易地共享数据和通信。

QThread是Qt框架中用于创建和管理线程的类。可以用QThread轻松地创建新的线程并将任务分配给它们还可以创建自己的线程类并从QThread继承,也可以使用QThread的静态函数创建线程。

QThread的主要优点是简单易用,能轻松地创建和管理线程。它提供了一些方法来控制线程的生命周期,包括start()和quit()方法来启动和停止线程,以及wait()方法来等待线程完成。

QThread还提供了一些信号来管理线程。例如,finished()信号在线程完成执行后发出,error()信号在线程发生错误时发出。



2.QThread示例一

在这个示例中有两个自定义线程:IncrementThread和DoubleThread,它们分别用于增加数字和将数字乘以2。这些线程都使用QThread类,并且都包含一个信号,以便在线程运行时向主线程发送结果。

#include <QObject>
#include <QThread>
#include <QVector>

class IncrementThread : public QThread {
    Q_OBJECT

public:
    IncrementThread(int count) : count(count) {}

signals:
    void countChanged(int count);

protected:
    void run() {
        for (int i = 1; i <= count; ++i) {
            emit countChanged(i);
            msleep(1000);
        }
    }

private:
    int count;
};

class DoubleThread : public QThread {
    Q_OBJECT

public:
    DoubleThread(const QVector<int>& nums) : nums(nums) {}

signals:
    void doubled(int num);

protected:
    void run() {
        for (int num : nums) {
            emit doubled(num * 2);
            msleep(1000);
        }
    }

private:
    QVector<int> nums;
};

在MainWindow类中,我们将创建线程,并将它们的信号连接到槽函数onCountChanged和onDoubled中。每当IncrementThread发出onCountChanged信号时,onDoubled将在主线程中更新标签的文本,以显示当前进度。类似地,每当DoubleThread发出doubled信号时,onDoubled将在主线程中更新标签的文本,以显示结果。

我们在构造函数中创建这两个线程,将它们的信号连接到槽函数中,并启动线程。

#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);

    ui->label->setText("Waiting for results...");
    incrementThread = new IncrementThread(5);
    doubleThread = new DoubleThread({1, 2, 3, 4, 5});

    connect(incrementThread, &IncrementThread::countChanged, this, &MainWindow::onCountChanged);
    connect(doubleThread, &DoubleThread::doubled, this, &MainWindow::onDoubled);

    incrementThread->start();
    doubleThread->start();
}

void MainWindow::onCountChanged(int count)
{
    ui->label->setText(QString("Incrementing %1").arg(count));
    qDebug()  <<  "Incrementing " << count;
}

void MainWindow::onDoubled(int num)
{
    ui->label->setText(QString("Doubling %1").arg(num));
    qDebug()  << "Doubling " << num;
}

MainWindow::~MainWindow()
{
    delete ui;
}

下面是程序运行时控制台输出的信息,从中可以看出两个线程是交替运行的。

在这里插入图片描述



3.QThread示例二

在这个示例中,我们创建了一个Worker类,该类包含一个doWork槽函数,用于在新线程中执行任务。在main函数中,我们创建了一个新的QThread对象和一个Worker对象,并将Worker对象的doWork槽函数连接到QThread对象的started信号上。然后,我们将Worker对象通过moveThread()方法移动到新线程中,并启动新线程。在主线程中,我们也有一个类似的循环,用于展示主线程和新线程是同时执行的。在循环结束后,我们调用了quit函数,以停止新线程的事件循环,并使用wait函数等待线程完成。

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <chrono>

class Worker : public QObject
{
    Q_OBJECT
    
public:
    Worker(int num_iterations) : m_num_iterations(num_iterations) {}
    
public slots:
    void doWork() {
        for (int i = 0; i < m_num_iterations; i++) 
        {
            qDebug() << "Thread function is running...";
            QThread::msleep(1000);
        }
    }
    
private:
    int m_num_iterations;
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int num_iterations = 5;
    QThread thread;
    Worker worker(num_iterations);
    //把线程启动信号与worker的doWork槽函数绑定在一起
    QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork);
    
    //将woker对象移动到thread线程中执行
    worker.moveToThread(&thread);

    thread.start();

    for (int i = 0; i < num_iterations; i++) 
    {
        qDebug() << "Main thread is running...";
        QThread::msleep(500);
    }

    thread.quit();
    thread.wait();
    return a.exec();
}

注意,若调用moveToThread()方法将worker对象移动到某个线程,则worker不能指定parent指针。默认情况下,对象是在哪个线程创建的,就属于哪个线程。



4.线程同步

在这个示例中,我们将创建一个线程并在其中使用QWaitCondition和QMutex来实现线程同步。

#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QDebug>

class MyThread : public QThread {
public:
    void run() override {
    	//加锁
        QMutexLocker locker(&mutex);
        qDebug() << "Thread started";
        // Do some work here
        qDebug() << "Thread waiting";
        //线程挂起,直到调用wakeUp函数后,继续往下执行
        condition.wait(&mutex);
        qDebug() << "Thread resumed";
        // Do more work here
        qDebug() << "Thread finished";
    }

    void wakeUp() {
        QMutexLocker locker(&mutex);
        condition.wakeOne();
    }

private:
    QWaitCondition condition;
    QMutex mutex;
};

int main(int argc, char *argv[])
{
    MyThread thread;
    thread.start();

    // Do some other work here

    thread.wakeUp();
    thread.wait();
    return 0;
}

在这个示例中,我们创建了一个MyThread类,继承自QThread,重写了run()函数,在其中执行一些工作。在该函数中,我们获取了一个QMutexLocker对象,以确保线程同步,并输出一些信息来显示线程已启动。

然后,我们调用QWaitCondition的wait()方法,将线程挂起,直到其他线程调用了wakeUp()方法。在等待期间,我们释放了QMutexLocker,以允许其他线程对共享资源进行访问。

在wakeUp()方法中,我们获取了QMutexLocker对象,以确保线程同步,并调用QWaitCondition的wakeOne()方法,以唤醒一个挂起的线程。

在主线程中,我们创建了一个MyThread对象并调用start()方法来启动线程。然后,我们执行一些其他工作,并在最后调用wakeUp()方法来唤醒线程。最后,我们调用wait()方法等待线程完成。

这个示例演示了如何使用QWaitCondition和QMutex来实现线程同步。QWaitCondition用于等待线程唤醒,而QMutex用于保护共享资源并防止竞争条件。通过使用这些类,我们可以编写线程安全的代码,并避免死锁和竞争条件的问题。

以上就是本文的全部内容了,希望对大家有帮助,如有问题,欢迎指正!



版权声明:本文为zheng19880607原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。