阅读QtCreator--Concurrent预备知识

摘自:http://m.oschina.net/blog/67836

 

在QtCreator当中用到了不少的Concurrent(并发),比如编译时,搜索时等。其实在很多场合中都需要用到,一般是CPU去做一项大任务(花费较长时间)时相应用户操作。另一个重要用途就是在当前这个多核,甚至多CPU的年代,并行变成成为一种时尚了,它也确实提高了应用程序的性能。我的电脑是单CPU,2核心4线程,所以相比单应用程序,应该可以将性能提高将近4倍(当然不会是4倍的)。我所听过的有很多库是这方面的,比如CUDA,OpenCL,OpenMP。Qt是怎么做的还真不知道,望高手指教。首先来测试下:

#include <QtCore/QCoreApplication>
#include <QtConcurrentRun>
#include <QtConcurrentMap>
#include <qmath.h>
#include <QFuture>
#include <QVector>
#include <QTime>
#include <QObject>
#include <QFutureWatcher>
#include "myslot.h"
QTime t;
void test(const int &N)
{
    int k = N * 100;
    int v = 0;
    for(int i = 0; i < k; ++i)
        ++v;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MySlot ms;
    QVector<int> datas(1000, 12343);
    t.start();
    for(int i = 0; i < 1000; ++i)
    {
        test(datas[i]);
    }
    printf("%d ms\n", t.elapsed());
    t.restart();
    QFutureWatcher<void> *pW = new QFutureWatcher<void>;

    QObject::connect(pW, SIGNAL(finished()), &ms, SLOT(onFinish()));
    QFuture<void> future = QtConcurrent::map(datas, test);
    pW->setFuture(future);
    return a.exec();
}

这里的MySlot里面就有一个onFinish()而已,完成时间输出,虽然QTime计时并不准确,但是这里已经可以看出问题了。

image

提高了将近三倍的速度。

这里分为三个部分来谈Concurrent在Qt中的用法(QThread在这里就不谈了,有点low-level,灵活性虽强,但不好控制,需要较好的多线程经验)。

1. QtConcurrent::run()

QtConcurrent::run()使用Qt的全局线程池,找到一个空闲线程执行函数。在操作系统中,是有流水线技术的,那么IO操作与CPU运算是可以同时进行的,如果我们的操作中有这两种操作,就可以使用QtConcurrent并发完成。看下面的函数原型:

QFuture<T> QtConcurrent::run ( Function function, ... )

第一个是函数名,接下来是不定长的参数列表。每个线程就去执行这个函数,直到完成任务线程归还给线程池。这里就有一个问题,假如我现在有很多任务,那么,需要多少个线程来做这些任务呢?当然,并不是开辟的线程越多越好的,开辟过多的线程不仅浪费资源,同时也会使CPU因切换时间片而性能下降。为了减少线程数量,我可以将任务分成N组,这个N的取值可以根据具体的计算机的CPU来确定,我的是4核心,可以使用4个线程4个分组(这个不是最好的,因为没有考虑到IO与CPU并发的问题)。Qt中有一个函数可以返回一个较合适的CPU线程数量QThread::idealThreadCount(),他返回的就是CPU的核心数量。
如果希望在每个组里根据当前处理状态来通知消息,可以使用QApplication::postEvent(), QApplication :: sendEvent()或者QMetaObject::InvokeMethod(),还有signal-slot,他们各有各自的好处。

postEvent()类似与windows中的PostMessage(),使用非阻塞的方式发送消息到消息队列当中,相反SendMessage()就是阻塞的方式了,知道消息被处理之后才会返回。但是Qt中的sendEvent()有些特别,如果在别的线程sendEvent(),你会得到这样的ASSERT:
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread c33f640. Receiver '' (of type 'Widget') was created in thread 277818"。
在Qt高级编程中有这样的一些话,“sendEvent()方法立刻分派消息--但是应该小心使用,或者就不使用,比如在多线程编程中使用sendEvent()会使事件句柄归发送者线程拥有,而不是接收者线程。并且没有事件压缩或者重排序,sendEvent()不会删除事件,因此应该在栈上创建事件。”
所以还是用postEvent比较好,具体用法参考Qt Assistant。

invokeMethod()的函数原型非常的长,如下

bool QMetaObject::invokeMethod ( QObject * obj, const char * member, Qt::ConnectionType type,QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument() ) [static]。
也就是说invokeMethod最多支持10个参数,该函数还有不少的重载。具体使用方法如下:

 QString retVal;
 QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection,
                           Q_RETURN_ARG(QString, retVal),
                           Q_ARG(QString, "sqrt"),
                           Q_ARG(int, 42),
                           Q_ARG(double, 9.7));

第三个参数是链接类型,在多线程中Qt::QueuedConnection是经常使用的。Q_RETURN_ARG只有在使用Qt::DirectConnection才会有效。

 

2. 使用QRunnable

使用QRunnable就需要派生一个子类了,然后重写虚函数run()。在run函数中完成要做的任务,但是QRunnable不是QObject的子类,那么只能用invoke方法了和自定义事件(custom event)。另外在run结束之后,线程池将会删除这个runnable对象,那么在构造函数里使用setAutoDelete(false)可以将控制权转交给用户。创建任务时,使用的方法是QThreadPool::globalInstance()->start(MyRunnableObj);

使用QtConcurrent::run()方法可以返回一个QFuture<T>, 我们可以使用QFutureWatcher<T>来跟踪处理的过程,而使用QRunnable类就需要我们自己管理了。

3. 使用QtConcurrent监视进度

有四种方式来监视任务进度,filter,mapper,reducer,function。最后一种是无法跟踪具体进度的,只能监视到任务开始与任务结束。这里分别说一下前三者的用法。
给定一个集合如QList,QVector等,通过一个过滤filter函数的返回值返回一个新的集合;mapper返回新类型的集合,reducer就是将集合中的值merge成一个值(比如求和等,这个值也可以是一个新的集合,那么所谓的值就是集合)。QtConcurrent::run()等方法都会返回一个QFuture<T>类型的对象,直接访问该对象将会阻塞,那么就要使用非阻塞的QFutureWatcher<T>来监视,它所监视的事件有paused, resumed, canceled。其中QFuture<T>中的T需要是filter等操作的结果类型,QFutureWatcher<T>就需要和他监视的QFuture的类型相同才对。来看一下《Qt高级编程》中的示意图:

image

这里有一个地方要注意:在filtered()之前,我们就应该将watcher的信号链接到相应的槽上去。下面是具体用法(以filtered为例):

函数原型

QFuture<T> QtConcurrent::filtered ( const Sequence & sequence, FilterFunction filterFunction );
QFuture<T> QtConcurrent::filtered ( ConstIterator begin, ConstIterator end, FilterFunction filterFunction )

说明前边的参数可以是一个集合,或者是两个const的迭代器,最后一个参数就是相应的过滤函数。过滤函数的圆形需要是这样的:

bool function(const T &t);

用法如下(来自Qt Assistant):

bool allLowerCase(const QString &string)
 {
     return string.lowered() == string;
 }

 QStringList strings = ...;
 QFuture<QString> lowerCaseStrings = QtConcurrent::filtered(strings, allLowerCase);

 

这里学会了一种叫函数对象的东东,上一段代码先看,其实就是重载()操作符,另外typedef 该操作符函数的返回值为result_type。

 
 
 
 
  
  
struct StartsWith
 {
     StartsWith(const QString &string)
     : m_string(string) { }

     typedef bool result_type;

     bool operator()(const QString &testString)
     {
         return testString.startsWith(m_string);
     }

     QString m_string;
 };

 QList<QString> strings = ...;
 QFuture<QString> fooString = QtConcurrent::filtered(images, StartsWith(QLatin1String("Foo")));
 
 

好,这部分就做这么多记录,然后准备把QtCreator的ProgressManager抠出来用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值