线程和并行编程之Qt中的多线程技术(三)

Qt主要提供了如下线程相关的类:

1.QThread:低级别的API(开启事件循环)

QThread是Qt中所有线程控制的基础,每个线程实例代表一个线程。有如下两种实现方式:

  1. 直接实例化:实例化的线程开启事件循环,允许QObject的槽函数在另外的线程中被调用。
  2. 间接子类化:在启动事件循环前,初始化线程;或者不开启事件循环,直接运行代码。

2.QThreadPool和QRunnable:可复用的线程

为了避免频繁创建和析构线程所带来的性能损耗,QTheadPool提供一种可线程复用的线程池。

线程池使用步骤:

  1. 子类化QRunnable;
  2. 重新实现QRunnable的run函数;
  3. 调用QThreadPool::start把QRunnable子类放入到队列中;
  4. 线程可用时,QRunnable::run中的代码会在该线程中运行。

线程池实例的获取:

  1. 每个Qt程序中默认有全局的线程池,通过QThreadPool::globalInstance()获取。它会根据CPU核数自动的分配线程的数量
  2. 自主创建并管理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(简化)算法可自动的将计算分发到可用的处理器中。主要有如下核心类(函数):

  1. QFuture:获取函数的执行结果,查询计算过程,暂停/恢复/取消计算。
  2. QFutureWatcher:使用信号槽机制与QFuture交互。
  3. 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.方案对比
特性QThreadQThreadPoolQtConcurrent::runQt 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线程发送数据
  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值