Qt主要提供了如下线程相关的类:
1.QThread:低级别的API(开启事件循环)
QThread是Qt中所有线程控制的基础,每个线程实例代表一个线程。有如下两种实现方式:
- 直接实例化:实例化的线程开启事件循环,允许QObject的槽函数在另外的线程中被调用。
- 间接子类化:在启动事件循环前,初始化线程;或者不开启事件循环,直接运行代码。
2.QThreadPool和QRunnable:可复用的线程
为了避免频繁创建和析构线程所带来的性能损耗,QTheadPool提供一种可线程复用的线程池。
线程池使用步骤:
- 子类化QRunnable;
- 重新实现QRunnable的run函数;
- 调用QThreadPool::start把QRunnable子类放入到队列中;
- 线程可用时,QRunnable::run中的代码会在该线程中运行。
线程池实例的获取:
- 每个Qt程序中默认有全局的线程池,通过QThreadPool::globalInstance()获取。它会根据CPU核数自动的分配线程的数量
- 自主创建并管理QThreadPool对象
代码实例:
#include <QRunnable>
#include <QThread>
#include <QDebug>
#include <QThreadPool>
class HelloWorldTask : public QRunnable
{
void run()
{
qDebug() << "Hello world from thread: " << QThread::currentThread();
}
};
void testThreadPoolDemo()
{
qDebug() << "testThreadPoolDemo...";
qDebug() << "main thread: " << QThread::currentThread();
HelloWorldTask *hello = new HelloWorldTask();
// 线程池会自动删除hello
QThreadPool::globalInstance()->start(hello);
};
执行结果:
testThreadPoolDemo...
main thread: QThread(0x717498)
Hello world from thread: QThread(0x7409e8, name = "Thread (pooled)")
3.QtConcurrent:高级别的API
Qt Concurrent提供了高级的方式来处理一些常见的并行计算模式:map(映射)、filter(过滤)、reduce(简化)。不同于QThread和QRunnable,它不会使用低级别的线程原语,如互斥量、信号量等。
Qt Concurrent的map(映射)、filter(过滤)、reduce(简化)算法可自动的将计算分发到可用的处理器中。主要有如下核心类(函数):
- QFuture:获取函数的执行结果,查询计算过程,暂停/恢复/取消计算。
- QFutureWatcher:使用信号槽机制与QFuture交互。
- QtConcurrent::run():在另外一个线程中执行函数。只能在一个线程中执行,不能查询计算过程,不能暂停/恢复/取消计算。
代码示例,统计文件的单词:
#include <QList>
#include <QMap>
#include <QTextStream>
#include <QString>
#include <QStringList>
#include <QDir>
#include <QTime>
#include <QApplication>
#include <QDebug>
#include <qtconcurrentmap.h>
using namespace QtConcurrent;
//递归搜索文件
QStringList findFiles(const QString &startDir, QStringList filters)
{
QStringList names;
QDir dir(startDir);
foreach (QString file, dir.entryList(filters, QDir::Files))
names += startDir + '/' + file;
foreach (QString subdir, dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot))
names += findFiles(startDir + '/' + subdir, filters);
return names;
}
typedef QMap<QString, int> WordCount;
//单线程检索单词
WordCount singleThreadedWordCount(QStringList files)
{
WordCount wordCount;
foreach (QString file, files) {
QFile f(file);
f.open(QIODevice::ReadOnly);
QTextStream textStream(&f);
while (textStream.atEnd() == false)
foreach (const QString &word, textStream.readLine().split(' '))
wordCount[word] += 1;
}
return wordCount;
}
//统计一个文件内的单词数量。该函数被多个线程调用,一定是线程安全的!
WordCount countWords(const QString &file)
{
QFile f(file);
f.open(QIODevice::ReadOnly);
QTextStream textStream(&f);
WordCount wordCount;
while (textStream.atEnd() == false)
foreach (const QString &word, textStream.readLine().split(' '))
wordCount[word] += 1;
return wordCount;
}
//归纳所有的映射结果到最总结过。该函数只被一个线程调用一次!
void reduce(WordCount &result, const WordCount &w)
{
QMapIterator<QString, int> i(w);
while (i.hasNext()) {
i.next();
result[i.key()] += i.value();
}
}
int main(int argc, char** argv)
{
QApplication app(argc, argv);
qDebug() << "finding files...";
QStringList files = findFiles("../../", QStringList() << "*.cpp" << "*.h");
qDebug() << files.count() << "files";
qDebug() << "warmup";
{
QTime time;
time.start();
WordCount total = singleThreadedWordCount(files);
}
qDebug() << "warmup done";
int singleThreadTime = 0;
{
QTime time;
time.start();
WordCount total = singleThreadedWordCount(files);
singleThreadTime = time.elapsed();
qDebug() << "single thread" << singleThreadTime;
}
int mapReduceTime = 0;
{
QTime time;
time.start();
WordCount total = mappedReduced(files, countWords, reduce);
mapReduceTime = time.elapsed();
qDebug() << "MapReduce" << mapReduceTime;
}
qDebug() << "MapReduce speedup x" << ((double)singleThreadTime - (double)mapReduceTime) / (double)mapReduceTime + 1;
}
如何选择?
如何选择线程实现方式跟线程的使用目的及线程的生命周期有关系。下面给了各种线程实现的对比及使用案例;
1.方案对比
特性 | QThread | QThreadPool | QtConcurrent::run | Qt Concurrent (Map, Filter, Reduce) |
---|---|---|---|---|
实现语言 | C++ | C++ | C++ | C++ |
指定线程优先级 | 是 | 是 | ||
内部可运行事件循环 | 是 | |||
通过信号槽更新数据 | 是 | |||
通过信号槽控制线程 | 是 | |||
通过QFuture监控线程 | 部分 | 是 | ||
内置暂停/取消/恢复 | 是 |
2.使用案例
线程生命周期 | 操作 | 方案 |
---|---|---|
调用一次 | 在另外一个线程中执行函数,并更新进度数据 | 1.重新实现QThread::run,启动QThread,发送信号更新进度条; 2.重新实现QRunnable::run,添加到线程池QThreadPool,更新进度条; 3.QtConcurrent::run执行函数,更新进度条; |
调用一次 | 在另外一个线程中执行函数,并获取执行结果 | 1.QtConcurrent::run执行函数; 2.函数执行后,使用QFutureWatcher发送结束信号; 3.使用QFutureWatcher::result获取执行结果 |
调用一次 | 使用所有可用处理器,对用容器中的每一项执行操作 | 1.使用QtConcurrent::filter选择容器中的元素; 2.使用QtConcurrent::map对每个元素操作; 3.使用QtConcurrent::filteredReduced()/QtConcurrent::mappedReduced()将结果汇总 |
永久的 | 在一个线程中根据请求或者数据的不同,执行不同的任务 | 1.子类化QObject创建一个工作单元; 2.实例化工作单元并且创建QThread,讲工作单元移入到QThread中; 3.通过信号槽与工作单元发送数据/命令 |
永久的 | 在线程中重复执行耗时操作,线程无需接受外部信号或事件 | 1.重新实现QThread::run,执行while循环; 2.启动线程; 3.通过信号槽向GUI线程发送数据 |