QThread 和 QRunnable 都是 Qt 框架中用于多线程编程的类,它们之间有以下不同点:
-
继承关系不同
QThread 继承自 QObject 类,而 QRunnable 没有父类。 -
实现方式不同
QThread 是一个完整的线程实现,包含了线程的创建、启动、停止、等待等功能。而 QRunnable 是一个任务接口,需要在 QThread 中使用 QThreadPool 或者手动创建线程池来执行任务。 -
线程数控制不同
使用 QThread 时,需要手动创建线程,因此可以直接控制线程数。而使用 QThreadPool 和 QRunnable 时,线程池会自动管理线程,可以通过设置最大线程数来控制并发执行的任务数。 -
取消任务的方式不同
使用 QThread 时,可以通过调用 QThread 的 quit() 方法或者设置一个标志位来取消线程的执行。而在 QRunnable 中,需要使用 QThreadPool 的 cancel() 方法来取消任务的执行。
总的来说,QThread 适用于需要自行管理线程的情况,比如需要控制线程的生命周期、线程间通信等情况。而 QRunnable 和 QThreadPool 适用于需要管理一组任务的情况,比如大量短时间的计算任务、网络请求等。使用 QThreadPool 和 QRunnable 可以有效地管理线程池和任务队列,避免创建过多的线程导致系统负载过高。
QThread
优点:
可以使用信号槽进行通信
缺点:
需要自己管理资源,线程的创建和释放,都需要自己手动管理,并且,频繁的创建和删除会造成比较大的内存开销。
适用场景:
线程不会被频繁的创建和删除,常驻内存的线程。
QThread 有两种使用方式:
- 通过moveToThread()实现 (qt开发者更推荐的方式,所有的槽函数都在子线程执行)
- 通过子类继承QThread实现(只有构造函数和run函数在子线程执行)
moveToThread方式
#include <iostream>
#include <QThread>
#include <QDebug>
using namespace std;
class worker : public QObject {
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
qDebug() << "work Thread ID:" << QThread::currentThreadId();
QString result;
emit resultReady(result);
}
signals:
void resultReady(const QString& result);
};
class controller : public QObject {
Q_OBJECT
public:
controller() {
worker* w = new worker();
w->moveToThread(&_thread);
connect(&_thread, &QThread::finished, w, &QObject::deleteLater);
connect(this, &controller::begin, w, &worker::doWork);
connect(w, &worker::resultReady, this, &controller::handleResults);
_thread.start();
}
~controller() {
_thread.quit();
_thread.wait();
}
public slots:
void handleResults(const QString& result) { cout << "I'm handleResult" << endl; }
signals:
void begin(const QString& parameter);
private:
QThread _thread;
};
/************测试**************/
#include <QCoreApplication>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
qDebug() << "main Thread ID: " << QThread::currentThreadId();
controller c;
emit c.begin("");
return app.exec();
}
//输出
main Thread ID: 0x6848
work Thread ID: 0x5724
I'm handleResult
子线程必须在 event loop 里执行,因为子线程是由主线程创建的,如果主线程退出了,子线程会也会立即退出。不管是否执行完。
子类继承QThread方式
#include <iostream>
#include <QThread>
#include <QDebug>
using namespace std;
class myThread : public QThread {
Q_OBJECT
public:
myThread() {
connect(this, &myThread::finished, this, &myThread::deleteLater);
}
void run() override {
QString result;
qDebug() << "sub Thread ID:" << QThread::currentThreadId();
// do something
emit resultReady(result);
}
signals:
void resultReady(const QString&);
};
class myObject : public QObject {
Q_OBJECT
public:
myObject() {
myThread *mt = new myThread();
connect(mt, &myThread::resultReady, this, &myObject::handleResult);
mt->start();
}
public slots:
void handleResult(const QString& result) { cout << "I'm handleResult" << endl; }
};
/**********测试******************/
#include <QCoreApplication>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
qDebug() << "main Thread ID: " << QThread::currentThreadId();
myObject mo;
return app.exec();
}
//输出
main Thread ID: 0x639c
sub Thread ID: 0x75ec
I'm handleResult
注意:
实例化的子类是在创建线程的旧线程中,不是在新创建的子线程中,该线程的槽函数还是在主线程执行,所以, 不能直接在新建的线程中使用槽。要想在子线程执行槽函数,只能用moveToThread方式
实例化子类的构造函数和run()函数在不同的线程中运行,因此,假设有成员变量两个函数中都能访问,则需要注意,多线程中资源的访问问题。
QThreadPool与QRunnable
优点:
不用资源管理,QThreadPool 启动线程执行完成后会自动释放
缺点:
可能会形成多线程资源竞争
不能使用信号槽(可以通过继承QObject中使用)
适用场景:
QRunnable适用于线程任务量比较大,需要频繁创建线程的情况。QRunnable能有效减少内存开销。
代码示例
头文件:
#include <QRunnable>
#include <QThread>
#include <QDebug>
#include <QThreadPool>
#include <QSemaphore>
using namespace std;
class taskQueue {
friend class worker;
public:
taskQueue(bool useThread);
void calculate();
private:
void doTask(int i);
private:
QThreadPool* _pool;
bool _useThread;
QSemaphore _sema;
};
class worker : public QRunnable {
public:
worker(taskQueue* task, int parameter);
void run() override;
private:
taskQueue* _task;
int _parameter;
};
源文件:
taskQueue::taskQueue(bool useThread) : _useThread(useThread) {
if (_useThread) {
_pool = QThreadPool::globalInstance();
_sema.release(QThread::idealThreadCount());
}
}
void taskQueue::calculate() {
for (int i = 0; i < 10; i++) {
if (_useThread) {
_sema.acquire(); //当线程不够时,等待线程任务完成
_pool->start(new worker(this, i));
} else {
doTask(i);
}
}
_pool->waitForDone();
}
void taskQueue::doTask(int i) {
// do something
qDebug() << i << "sub thread" << QThread::currentThreadId();
_sema.release();
}
worker::worker(taskQueue *task, int parameter)
: _task(task), _parameter(parameter) {
}
void worker::run() {
_task->doTask(_parameter);
}
/************测试***********/
int main(int argc, char *argv[])
{
qDebug() << "main Thread ID: " << QThread::currentThreadId();
taskQueue q(1);
q.calculate();
}
//输出
main Thread ID: 0x4d3c
2 sub thread 0x5d18
1 sub thread 0x5a68
3 sub thread 0x3d4
4 sub thread 0x4ae0
6 sub thread 0x4f50
8 sub thread 0x480c
0 sub thread 0x7888
7 sub thread 0x2734
5 sub thread 0x7c10
9 sub thread 0x4c98
QtConcurrent
QtConcurrent是一个命名空间,提供了一些高级的 API,使得在编写多线程的时候,无需使用低级线程原语,如读写锁,等待条件或信号。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。这意味着今后编写的应用程序将在未来部署在多核系统上时继续扩展。
QtConcurrent::run 能够方便快捷的将任务丢到其它的线程中去执行,无需继承任何类,也不需要重写函数,使用非常简单。
QtConcurrent::run 是在另一个线程中执行任务,当前线程继续做别的运算。
个人对于 QtConcurrent::run 的理解感觉是:thread call thread。无论是在子线程还是在主线程,都可以调用 QtConcurrent::run,把相应的任务放在另外的线程执行。
run函数:
template <typename T>
QFuture<T> QtConcurrent::run(Function function, ...)
template <typename T>
QFuture<T> QtConcurrent::run(QThreadPool *pool, Function function, ...)
不传线程池的相当于:
QtConcurrent::run(QThreadPool::globalInstance(), function, ...);
QtConcurrent 的底层实现是 QThreadPool与QRunnable