QThread(QThreadPool)与std::thread比较

本文探讨了Qt中的QThread和std::thread两种线程处理方式,比较了它们的使用场景、优缺点,以及QThreadPool在任务并发管理中的作用。QThread适用于需要信号槽通信的长生命周期线程,而std::thread适合执行一次性任务和高效利用线程池。
摘要由CSDN通过智能技术生成

QThread与std::thread

现在有一个函数 fun(), 且希望将此函数运行在子线程中

Qt使用线程的常规方式:

  1. 继承QThread,重写run()方法,将fun函数放到run函数中,通过QThreadstart函数启动线程,那么run()方法中的函数体将会运行在子线程,此种方式开启的线程,并未启动事件循环, run函数执行结束,线程即退出;并发送出finished信号;(适合运行单个任务,比如计算某个耗时操作,执行完即可,不需要线程常驻)
  2. 使用QConcurrent::run,将函数交给线程池去执行,并可以使用QFutureQFutureWatcher来监测线程是否执行完成, run()函数执行结束时,QFutureWatcher也会发出finished信号通知
  3. 继承自QObject, 调用moveToThread(),将对象移动到子线程,则该object的信号和槽都将运行在子线程(有parent对象的object不能被移动); 此种方式启动的线程,会自动启动QThread的事件循环,需要主动调用子线程的quit()wait()函数来退出线程;(适合长时间运行或者需要持续存在并可能随时处理事件的线程,例如一直在子线程中处理某种计算参见另一篇文章
特殊方式
  1. 也可以不继承QThread,将fun函数放入lambda表达式中,不写第三个receiver参数, 如下:
QThread m_threadA;
connect(&m_threadA, &QThread::started, [this](){fun(); m_threadA.quit();});
m_threadA.start();

这一种connect写法, 因为没有第三个参数receiver,所以lambda表达式中的内容会在sender线程中执行,达到了在子线程中执行的目的;效果如同使用 std::thread t = std::thread(fun);
与第一种方式继承QThread并重写run()方法的区别在于:
继承QThread重写run()方法执行完成之后,线程会自动退出,并发送finished信号,而这种写法就必须主动调用quit,否则线程不会退出,不会发出finished信号;

  • QThread相对于std::thread的好处是,QThread提供了isFinished()以及isRunning()方法,用于访问线程是否执行结束,并提供了finished()信号,用于线程结束时主动通知其他对象;并且QThread还具有事件循环,可以让某些对象一直在子线程中运行,并可以使用信号与槽的方式与其他线程进行通信;
  • 而std::thread没有事件循环,也没有提供查询线程是否运行结束的函数,也没有在线程结束时主动通知外界的机制, std::thread运行完函数体,线程就会自动结束

std::thread 如何判断线程是否结束:

  • 如果使用C++11 std::asyncstd::future来运行您的任务,那么可以使用std::futurewait_for函数来检查线程是否仍在运行:
#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
   using namespace std::chrono_literals;

   /* Run some task on new thread. The launch policy std::launch::async
      makes sure that the task is run asynchronously on a new thread. */
   auto future = std::async(std::launch::async, [] {
       std::this_thread::sleep_for(3s);
       return 8;
   });

   // Use wait_for() with zero milliseconds to check thread status.
   auto status = future.wait_for(0ms);

   // Print status.
   if (status == std::future_status::ready) {
       std::cout << "Thread finished" << std::endl;
   } else {
       std::cout << "Thread still running" << std::endl;
   }
   auto result = future.get(); // Get result.
}
  • 如果必须使用std::thread,那么可以使用std::promise来获取未来的对象:
#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
   using namespace std::chrono_literals;

   // Create a promise and get its future.
   std::promise<bool> p;
   auto future = p.get_future();

   // Run some task on a new thread.
   std::thread t([&p] {
       std::this_thread::sleep_for(3s);
       p.set_value(true); // Is done atomically.
   });

   // Get thread status using wait_for as before.
   auto status = future.wait_for(0ms);

   // Print status.
   if (status == std::future_status::ready) {
       std::cout << "Thread finished" << std::endl;
   } else {
       std::cout << "Thread still running" << std::endl;
   }

   t.join(); // Join thread.
}
  • 还有一个与std::thread配合使用的std::packaged_task,它与std::function类似,但是它可以关联一个std::future, 使用起来比使用std::promise更简洁
#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
   using namespace std::chrono_literals;

   // Create a packaged_task using some task and get its future.
   std::packaged_task<void()> task([] {
       std::this_thread::sleep_for(3s);
   });
   auto future = task.get_future();

   // Run task on new thread.
   std::thread t(std::move(task));

   // Get thread status using wait_for as before.
   auto status = future.wait_for(0ms);

   // Print status.
   if (status == std::future_status::ready) {
       // ...
   }

   t.join(); // Join thread.
}
  • 或是使用一个bool变量来记录线程是否执行结束:
#include <thread>
#include <atomic>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    std::atomic<bool> done(false); // Use an atomic flag.

    /* Run some task on a new thread.
       Make sure to set the done flag to true when finished. */
    std::thread t([&done] {
        std::this_thread::sleep_for(3s);
        done = true;
    });

    // Print status.
    if (done) {
        std::cout << "Thread finished" << std::endl;
    } else {
        std::cout << "Thread still running" << std::endl;
    }

    t.join(); // Join thread.
}

QThreadPool

QThreadPool是Qt框架中的一个组件,它主要用于管理和复用线程资源,以优化并发任务的执行效率。当你有大量 短小的任务需要异步执行时,使用QThreadPool可以避免频繁创建和销毁线程所带来的开销,并能轻松控制同时运行的任务数量。

QThreadPool的工作原理:
  • 线程管理:
    QThreadPool维护了一个可复用的线程列表。
    当有新的QRunnable任务提交到线程池时,线程池会选择一个可用(即未执行任务)的线程来执行任务,如果没有可用线程并且线程总数没有达到预设的最大线程数,那么线程池将会创建一个新的线程来执行任务。

  • 任务调度:
    提交到线程池的任务必须是QRunnable的子类实例,每个任务都需要重写run()虚函数,其中包含实际的执行逻辑。
    QThreadPool按照先进先出(FIFO)的原则处理任务,除非任务优先级不同,高优先级的任务可能会提前执行。

  • 线程限制:
    开发者可以调用QThreadPool::setMaxThreadCount(int maxThreadCount)来设置线程池允许的最大线程数量,超过此数量的任务将排队等待。

QRunnable与QThread的对比说明

QRunnableQThread都是Qt库中用于实现多线程的工具,它们各自有特定的设计目标和使用场景,下面是对两者之间对比的详细说明:

QThread:
  • QThread是一个跨平台的线程类,它是QObject的一个子类,因此可以和其他QObject一样使用信号和槽进行通信,这是QThread的一大优势。
  • 使用QThread一般有两种方式:
    1.通过继承QThread类并重写run()方法,然后调用start()启动线程;当run()函数执行结束时,线程即结束
    2.是将工作逻辑封装在另外的QObject中,并将这个QObject移动到新创建的QThread实例的事件循环中,这样工作逻辑将在新线程的上下文中执行。

QThread适合长时间运行或者需要持续存在并可能随时处理事件的线程,例如后台服务、定时器等。

QRunnable:
  • QRunnable是一个轻量级的非QObject类,设计初衷是为了简化临时性、一次性任务的执行,不需要像QThread那样建立完整的对象层次结构。
  • QRunnable只包含一个纯虚函数void run(),你需要继承QRunnable并实现这个run()函数,用来定义线程的具体工作内容。QRunnable配合QThreadPool使用可以实现任务的并发执行。线程池负责调度和管理线程资源,当一个QRunnable任务提交给线程池时,池中的一个线程会执行该任务的run()方法,完成后线程将回到池中等待下一个任务。

QRunnable不适合需要长期存在的线程,因为它不支持信号和槽机制,这意味着无法像QThread那样直接在线程间发送信号来同步数据或通知事件。
QRunnable更适合执行短暂、独立、计算密集型的任务,特别是当任务数量巨大且可以很好地并行化时,通过QThreadPool能够有效地利用系统资源。

总结来说,QThread更适合构建复杂的多线程应用,特别是在需要使用信号槽机制来协调线程间交互的情况下;而QRunnable则更适合处理一次性、无状态、易于并行化的任务,它可以高效地利用线程池,减少系统资源消耗并提高并发性能。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值