QThread和std::thread

avatar
作者
筋斗云
阅读量:2

        在 Qt 中, 我们经常会用到多线程,这时候就需要纠结是使用 Qt 的 QThread 还是使用 C++ 标准库的 std::thread。

        这里记录一下我自己的理解,先介绍一下 QThread 和 std::thread 的使用方法,对比一下他们的不同,最后说一下我理解的应该怎么选择这两种方法。


QThread使用方法

        QThread 的使用有两种方法,一种是继承 QThread,然后重写 run() 函数,这种方法适合执行单个任务,比如计算某个耗时操作,执行完即可,不需要线程常驻。在 Qt5 之后 Qt 官方已经不推荐这种使用方法了,所以在这里也不赘述了。

        在 Qt5 之后,推荐的方法是创建一个工作类继承于 QObject,将工作对象通过 movetothread() 函数移动到一个新的线程中,这种方法更符合 Qt 的对象模型,并且更易于管理线程的生命周期和资源。下面是一个示例:

class Worker : public QObject {     Q_OBJECT public:     Worker();     ~Worker();      void doWorkA() {         qDebug() << "Worker thread ID:" << QThread::currentThreadId();         emit sigReadData("doWorkA");     }      void doWorkB() {         qDebug() << "Worker thread ID:" << QThread::currentThreadId();         emit sigResultReady();     }  signals:     void sigReadData(QString data);     void sigResultReady(); };
QThread *thread = new QThread(); Worker *worker = new Worker();  worker->moveToThread(thread);  // 连接信号槽 QObject::connect(worker, &Worker::sigReadData, this, &::); QObject::connect(worker, &Worker::sigResultReady, thread, &QThread::quit); QObject::connect(worker, &Worker::sigResultReady, worker, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater); // 线程完成后自动删除  qDebug() << "Main thread ID:" << QThread::currentThreadId();  thread->start(); worker->doWorkA(); worker->doWorkB();

        这个 Worker 类没有做任何特别的事情,但它包含所有必需的元素。在这个例子中, doWorkA() 被调用时可以做一些你自己的处理,处理完后发送 sigReadData() 给主线程,doWorkB() 完成后它会发出信号 sigResultReady(),然后该信号将连接到 QThread 的槽 quit(),触发 QThread 的事件循环退出。事件循环退出后,QThread::finished() 信号被触发,连接到槽 QObject::deleteLater(),自动删除 QThread 对象。

        顺便说一句,这里要注意的非常重要的一件事是你永远不应该在 QObject 类的构造函数中分配堆对象(使用 new)。如果在构造函数中new,会报错 QObject:Cannot create children for a parent that is in a different thread。这个报错是由于这个 new 分配是在主线程,而不是新的子线程 QThread 实例上的。这意味着新创建的对象是由主线程拥有的,而不是 QThread 实例。所以,应该在 Worker 类的函数或者槽函数中分配此类资源,例如在 doWorkA() 或者doWorkB() 中使用new。在这种情况下,当调用该对象时,该对象将位于新线程实例上,因此新的线程实例将拥有该资源。


std::thread使用方法

        std::thread 是 C++11 引入的标准线程库,用于创建和管理线程。它提供了一种轻量级的方法来实现多线程编程,适合需要高性能和低延迟的应用。以下是 std::thread 的基本使用方法,可以使用下面几种方法创建线程:

#include <iostream> #include <thread>  class MyClass { public:     void memberFunction() {         std::cout << "Hello from member function!" << std::endl;     } };  void threadFunction() {     std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl; }  int main() {     // 使用函数指针     std::thread t1(threadFunction);     std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;     if (t1.joinable()) {         t1.join();    // 等待线程完成     }      // 使用成员函数     MyClass obj;     std::thread t2(&MyClass::memberFunction, &obj); // 传递对象指针     if (t2.joinable()) {         t2.join();    // 等待线程完成     }      // 使用lambda表达式     std::thread t3([](){         std::cout << "Hello from thread!" << std::endl;     });     if (t3.joinable()) {         t3.join();    // 等待线程完成     }      return 0; }

        管理线程有两个函数,一个是上面已经使用过的 join(),另一个是 detach():

std::thread t(threadFunction); t.join(); // 阻塞主线程,直到 t 线程完成
std::thread t(threadFunction); t.detach(); // 将线程与当前线程分离,允许 t 独立运行 // 注意:必须确保分离的线程在程序退出前完成


QThread和std::thread的选择

        在我看来,QThread 不仅仅是一个简单的线程类,它更像一个线程管理器。提供了与 Qt 框架的无缝集成,特别是与信号槽机制的结合使用,使得多线程编程更加方便和高效。我们可以把部分功能封装在一个工作对象中,然后把这个对象 movetothread,这样通过这个对象调用封装的所有方法,都是在子线程中进行,我只需要通过Qt的信号槽把子线程中我需要的数据传出来接收就可以在主线程或者UI上显示,而不会卡住主线程或者UI。

        例如,我要控制一个打印机或者串口,需要连接设备、发送消息和一直接收消息等功能,我们把这几个功能封装在一个打印机类或者串口类中。创建对象,然后通过 movetothread() 把这个类放到子线程,这样我们就可以把连接、发送接收处理消息这些功能都放到子线程中,而不影响主线程,接收到的消息需要传给主线程并显示的话就发送信号给主线程。

        而对于一些简单的多线程任务,例如并行计算、独立的后台任务等,使用 std::thread 可以更加轻便、直接和高效。std::thread 相对轻量,没有额外的框架依赖,适用于需要高性能和低开销的多线程操作。并且,std::thread 是 C++ 标准的一部分,保证了跨平台的一致性。如果需要在不同的平台上开发和运行代码,std::thread 提供了一种标准化的方法。

        总结一下:

  • 选择 QThread:如果你需要与 Qt 对象和事件循环进行集成。
  • 选择 std::thread:如果你需要一个更通用、更轻量级的多线程解决方案。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!