问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Qt线程全解析:5大线程的性能对比与实战技巧!

创作时间:
作者:
@小白创作中心

Qt线程全解析:5大线程的性能对比与实战技巧!

引用
CSDN
1.
https://blog.csdn.net/m0_63998314/article/details/144927884

在现代软件开发领域,多线程编程已经成为提高程序性能和响应速度的关键手段之一。特别是在图形界面应用开发中,多线程技术能够有效地避免界面冻结和提升用户体验。而Qt作为一种跨平台的C++应用程序框架,提供了丰富的多线程支持,使得开发者能够轻松地构建高效、响应迅速的多线程应用。本文将深入探讨Qt中实现多线程的多种方法,并通过具体的代码示例帮助读者更好地理解和应用这些技术。

一、继承QThread类并重写run()方法

这是最传统也最常用的一种创建线程的方式。通过继承QThread类并重写其run()方法,可以自定义线程的行为。

具体步骤:

  1. 定义一个继承自QThread的子类,并实现其run()方法。run()方法是线程的入口点,所有需要在线程中执行的代码都应该放在这个方法中。
  2. 在主线程中创建这个自定义类的实例,然后调用start()方法启动线程。

示例代码

#include <QThread>
#include <QDebug>

class MyThread : public QThread {
    Q_OBJECT

public:
    MyThread(QObject *parent = nullptr) : QThread(parent) {}

protected:
    void run() override {
        // 在这里写入你的线程逻辑
        qDebug() << "Thread is running";

        // 例如,模拟一些计算
        for (int i = 0; i < 10; ++i) {
            // 做一些工作
        }
    }
};

// 使用线程的例子
int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    MyThread thread;
    thread.start(); // 启动线程

    return app.exec();
}

在这个例子中,MyThread类继承自QThread并重写了run()方法。在main函数中,创建了MyThread对象,并调用了start()方法来启动线程,这会自动调用run()方法。

二、使用moveToThread将对象移至特定线程

这种方法允许你将一个继承自QObject的普通对象移动到指定的线程中执行。这通常用于需要在多个线程间共享资源的情况。

具体步骤:

  1. 创建一个继承自QThread的线程类。
  2. 在你想要移动到该线程的对象中,使用moveToThread()方法。
  3. 连接信号槽到相应的线程。

示例代码

// myworker.h
#ifndef MYWORKER_H
#define MYWORKER_H

#include <QObject>

class MyWorker : public QObject {
    Q_OBJECT
public:
    explicit MyWorker(QObject *parent = nullptr);

signals:
    void workFinished(const QString &result);

public slots:
    void doWork(const QString &parameter);
};

#endif // MYWORKER_H

// myworker.cpp
#include "myworker.h"
#include <QThread>

MyWorker::MyWorker(QObject *parent) : QObject(parent) {}

void MyWorker::doWork(const QString &parameter) {
    // 在这里执行耗时的任务
    QString result = "处理完毕: " + parameter;
    emit workFinished(result); // 当任务完成时发出信号
}

// main.cpp
#include <QCoreApplication>
#include <QDebug>
#include "myworker.h"

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

    MyWorker *worker = new MyWorker;
    QThread *thread = new QThread;

    worker->moveToThread(thread); // 将worker对象移动到新线程

    // 连接线程启动
    QObject::connect(thread, &QThread::started, worker, &MyWorker::doWork);
    // 连接线程结束
    QObject::connect(worker, &MyWorker::workFinished, thread, &QThread::quit);
    // 连接线程结束到lambda表达式处理
    QObject::connect(worker, &MyWorker::workFinished, [&](const QString &result) {
        qDebug() << result;
    });
    // 连接线程结束,使得线程进入终止状态
    QObject::connect(worker, &MyWorker::workFinished, thread, &QThread::finished);

    thread->start(); // 启动线程

    return a.exec();
}

在这个例子中,MyWorker类是一个工作类,它在新线程中执行任务。我们创建了MyWorker和QThread对象,并将MyWorker移动到了QThread线程中。我们还定义了当工作完成时如何响应,通过发送workFinished信号,并连接了相应的槽函数来处理结果。

三、使用QThreadPool搭配QRunnable(线程池)

线程池是一种预先创建好一定数量线程的技术,可以重复利用这些线程来执行任务,从而减少创建和销毁线程带来的开销。Qt通过QThreadPool类提供了对线程池的支持。你可以创建一个或多个QRunnable对象,并将其提交给线程池执行。

示例代码

#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class MyRunnable : public QRunnable {
public:
    void run() override {
        // 在这里写入需要在新线程中执行的代码
        qDebug() << "Thread ID: " << QThread::currentThreadId();
    }
};

int main(int argc, char *argv[]) {
    // 初始化Qt应用程序
    QCoreApplication app(argc, argv);

    // 创建一个线程池
    QThreadPool threadPool;

    // 设置线程池中的最大线程数
    // 如果不设置,Qt将自动管理线程数
    threadPool.setMaxThreadCount(4);

    // 创建一个QRunnable对象
    MyRunnable *myRunnable = new MyRunnable();

    // 将QRunnable加入到线程池中
    threadPool.start(myRunnable);

    // 运行事件循环
    return app.exec();
}

这段代码演示了如何在Qt中使用QThreadPool和QRunnable来实现多线程。首先,我们定义了一个继承自QRunnable的MyRunnable类,并重写了run方法。在main函数中,我们创建了一个QCoreApplication实例,初始化了Qt环境。接着,我们创建了一个QThreadPool实例,并设置了最大线程数。然后我们创建了一个MyRunnable对象,并将其加入到线程池中。最后,我们运行了Qt的事件循环。

四、使用QTimer与事件循环相结合

在Qt中,可以使用QThread和QTimer来实现多线程,其中一个线程用于事件循环,另一个线程用于定时任务的执行。

示例代码

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <iostream>

// 定时任务线程
class TimerThread : public QThread {
    Q_OBJECT
public:
    void run() override {
        QTimer timer;
        connect(&timer, &QTimer::timeout, []() {
            std::cout << "Timer task executed." << std::endl;
        });
        timer.start(1000); // 每秒执行一次

        exec(); // 进入事件循环
    }
};

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

    TimerThread timerThread;
    timerThread.start(); // 启动定时任务线程

    return app.exec(); // 主线程进入事件循环
}

在这个例子中,TimerThread继承自QThread,在其run函数中创建了一个QTimer对象,并连接了它的timeout信号到一个lambda函数。定时器每秒钟触发一次,并打印一条消息。QTimer::start启动定时器。exec()调用使得线程进入事件循环,保持线程活跃,处理定时事件。主线程使用QCoreApplication进入事件循环,负责处理主要的用户界面事件。这样,通过多线程,事件处理和定时任务可以并行执行,提高程序的响应性和效率。

五、使用std::async与std::future(C++11标准库)

#include <QtWidgets/QMainWindow>
#include <QFuture>
#include <QFutureWatcher>
#include <iostream>
#include <future>

class MyMainWindow : public QMainWindow {
    Q_OBJECT

public:
    MyMainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
        QFutureWatcher<int> *watcher = new QFutureWatcher<int>(this);
        connect(watcher, &QFutureWatcher<int>::finished, [this, watcher]() {
            std::future<int> future = watcher->future();
            if (future.valid() && future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
                int result = future.get();
                std::cout << "异步任务返回结果: " << result << std::endl;
            }
        });

        QObject::connect(this, &MyMainWindow::startAsync, watcher, &QFutureWatcher<int>::start, Qt::QueuedConnection);
        QObject::connect(watcher, &QFutureWatcher<int>::resultReady, [this, watcher](int result) {
            // 处理结果
            std::cout << "异步任务返回结果: " << result << std::endl;
        });

        QThread::currentThread()->setObjectName("GUI Thread");
        watcher->start(); // 启动异步任务
    }

signals:
    void startAsync();
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    MyMainWindow mainWin;
    mainWin.show();

    return app.exec();
}

// 在另外一个文件中,你需要定义你的异步任务函数
int heavyWork() {
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

在这个例子中,我们创建了一个QFutureWatcher,并且通过信号和槽连接startAsync信号到QFutureWatcher的start槽函数。当MyMainWindow对象发出startAsync信号时,QFutureWatcher会开始执行异步任务。我们使用std::async来启动任务,并通过std::future获取结果。注意,在Qt中,你应该避免在GUI线程之外直接访问或修改GUI元素,所以我们使用Qt::QueuedConnection来安排任何对GUI的访问发生在GUI线程中。

六、应用场景分析

让我们从Qt的QThread说起。QThread是Qt用于创建和管理线程的类。开发者可以通过继承QThread并重写其run()函数来定义线程执行的任务。这种方法直观且易于理解,适合简单的多线程需求。然而,直接使用QThread的缺点在于它为每个线程任务创建一个新的线程实例,这可能会导致资源消耗过大,特别是在大量短生命周期的任务情况下。

为了更高效地管理线程资源,Qt引入了moveToThread方法。这个方法允许将一个对象移动到指定的线程中运行。当对象被移动到新线程后,该线程将接管这个对象的事件处理。这种机制特别适用于那些需要在特定线程中执行,但又不需要频繁切换线程的对象。通过moveToThread,可以有效减少线程间切换带来的开销,提高程序性能。

QThreadPool是另一种高效的多线程管理方式。它通过维护一个线程池来重复利用线程,避免了频繁创建和销毁线程的开销。开发者需要子类化QRunnable并重写其run()函数以实现具体任务逻辑。QThreadPool可以自动处理任务的调度和线程的管理,使开发者能够专注于业务逻辑的实现,而不必关心底层的线程操作。

结合QTimer和事件循环是另一种在Qt中实现多线程任务的方式。通过QTimer::singleShot()函数,可以在指定的时间后执行一个槽函数,这种方式简单易用,适合于需要延迟执行的场景。然而,需要注意的是,QTimer默认在当前线程的事件循环中运行,如果需要在特定线程中运行,可能需要结合moveToThread等方法进行适当的调整。

我们来看一下使用C++标准库中的std::async和std::future。与Qt的多线程工具相比,std::async和std::future提供了一种更通用的方式来异步执行任务,并且能够轻松获取任务的返回结果。这种方法的优势在于它的灵活性和强大的功能,但它要求开发者对C++标准库有较深的理解。此外,由于std::async不依赖于Qt的事件循环,因此它可能在与Qt的其他部分集成时遇到一些挑战。

七、总结

Qt提供了多种多线程编程的方法,每种方法都有其适用场景和优缺点。选择合适的多线程实现方式需要考虑任务的性质、性能要求以及开发效率等因素。无论是使用QThread、moveToThread、QThreadPool搭配QRunnable、结合QTimer与事件循环,还是使用std::async与std::future,关键在于理解它们的工作原理和适用条件,以便在实际应用中做出最佳选择。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号