QThread与std::thread
现在有一个函数 fun(), 且希望将此函数运行在子线程中
Qt使用线程的常规方式:
- 继承
QThread
,重写run()
方法,将fun
函数放到run
函数中,通过QThread
的start
函数启动线程,那么run()
方法中的函数体将会运行在子线程,此种方式开启的线程,并未启动事件循环
,run
函数执行结束,线程即退出;并发送出finished
信号;(适合运行单个任务,比如计算某个耗时操作,执行完即可,不需要线程常驻) - 使用
QConcurrent::run
,将函数交给线程池去执行,并可以使用QFuture
与QFutureWatcher
来监测线程是否执行完成,run()
函数执行结束时,QFutureWatcher
也会发出finished
信号通知 - 继承自
QObject
, 调用moveToThread()
,将对象移动到子线程,则该object
的信号和槽都将运行在子线程(有parent对象的object不能被移动); 此种方式启动的线程,会自动启动QThread的事件循环
,需要主动调用子线程的quit()
、wait()
函数来退出线程;(适合长时间运行或者需要持续存在并可能随时处理事件的线程,例如一直在子线程中处理某种计算)参见另一篇文章
特殊方式
- 也可以不继承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::async
和std::future
来运行您的任务,那么可以使用std::future
的wait_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的对比说明
QRunnable
和QThread
都是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
则更适合处理一次性、无状态、易于并行化的任务,它可以高效地利用线程池,减少系统资源消耗并提高并发性能。