Qt中的多线程技术

翻译自Qt帮助文档:Mutithreading Technologies in Qt


Qt提供了许多类和函数来处理线程。下面是实现多线程应用程序的4个不同的方法。


1. QThread: 低级API与可选的事件循环

QThread是Qt所有线程控制的基础,每个QThread实例代表并控制一个线程。

QThread可以被直接实例化或者子类化。执行实例化一个QThread即提供一个并行的事件循环,这个事件循环允许第二个线程调用QObject槽。执行子类化QThread即允许应用程序在开始事件循环之前初始化一个新的线程,或者允许应用程序没有事件循环也能运行并行代码。


2. QThreadPool 和QRunnable:线程重用

频繁的创建和销毁线程开销很大,为了减少这种开销,现有的线程可以用来执行新任务。QThreadPool是一个可重用线程的集合。

为了运行QThreadPool中线程的代码,重新实现QRunnable::run()并实例化Runnable的子类。使用QThreadPool::start()将QRunnable放入QThreadPool的运行队列。当一个线程可用时,QRunnable::run()中的代码将会在这个线程中执行。

每个Qt应用程序有一个全局的线程池,这个线程池可以通过QThreadPool::globalInstance()来访问。这个全局的线程池自动维护着一个最优数量,这是线程的数量,它是基于CPU的核的数量的。然而,我们可以显示的创建和管理一个独立的QThreadPool。


3.Qt并发:使用高级API

Qt并发模块提供了一些高级函数,这些函数处理常见的并行计算模式:map,filter 和 reduce。不像使用QThread和QRunnable,这些函数从来不需要使用低级线程基元,例如互斥量(mutexs)和信号量(semaphores)。相反的,它们返回一个QFuture对象,当函数准备好时,这个对象可以用来获取函数的结果。QFuture也可以用来查询计算的进展并暂停/恢复/取消这个计算。为了方便,QFutureWatcher通过信号和槽来与QFuture交互。

Qt并发的map,filter 和 reduce 算法自动的在所有可用的处理器核上分发计算,因此在多核系统中,今天写的应用程序当被部署后将来会继续scale(vi,翻译成衡量,攀登,剥落?分发?)。

这个模块也提供了QtConcurrent::run() 函数,这个函数可以运行其他线程的任何函数。然而,QtConcurrent::run()只支持一个子集,这个子集由map,filter 和 reduce 函数的功能特性组成。QFuture可用来检索函数的返回值,并检查线程是否正在运行。然而,调用QtConcurrent::run() 仅仅使用一个线程,这个调用不能被暂停/恢复/取消,也不能查询调用的进度。


4.WorkerScript:QML中的线程

WorkerScript QML类型让JavaScript代码与GUI线程并行运行。

每个WorkerScript 实例可以拥有一个 .js 脚本。当调用WorkerScript::sendMessage() 时这个脚本将会在一个单独的线程中运行(一个单独的QML上下文)。当脚本运行完成时,它会向GUI线程发送一个回复,这个GUI线程将会调用WorkerScript::onMessage() 信号处理程序。

使用WorkerScript 和 使用一个已经被移到别的线程中的工人 QObject相似。数据通过信号在线程间转移。

参考WorkerScript 文档中怎样实现脚本的详细信息的,以及那些可以在线程间传值的数据列表。


选择一个合适的方法

如上所述,Qt提供了不同的开发多线程应用程序的方法。对于一个应用程序,最好的方法是由新线程的意图以及该线程的生命周期决定的。下面的表格对Qt的线程技术做了个比较,紧跟这些线程技术后面的是推荐的一些示例用例的解决方案。

Comparison of Solutions

FeatureQThreadQRunnable 和 QThreadPoolQtConcurrent::run()Qt Concurrent(Map, Fillter,Reduce)   WorkerScript
APIC++C++C++C++QML
可以指定线程优先级?Yes Yes    
线程可以运行事件循环Yes    
线程可以接收信号更新过来的数据Yes(通过工人QObject接收)   Yes(通过WorkerScript接收)
线程可以通过信号被控制?Yes(通过Qthread接收)  Yes(通过QFutureWatcher接收) 
线程可以通过QFuture被监控?  部分的Yes 
内嵌功能来暂停/恢复/取消?   Yes 


















Example Use Cases

Lifetime of threadOperationSolution
One call在另一个线程中运行一个新的线性函数,可以在运行时进行进度更新Qt提供了不同的解决方案:
1.将这个函数放在重新实现的QThread::run()中,然后start这个QThread。发送信号来更新进度。或者
2. 将这个函数放在重新实现的QRunnable::run()函数中,然后将QRunnable添加到QThreadPool中。写到一个线程安全的变量中来更新进度。或者
3. 使用QtConcurrent::run()运行这个函数,写到一个线程安全的变量中来更新进度。
One call在另一个线程中运行一个已经存在的函数,并获取它的返回值使用QtConcurrent::run()来运行这个函数。当函数返回时让QFutureWatcher发送finished()信号,并调用QFutureWatcher::result()来获取函数的返回值。
One call对一个容器中所有项执行一个操作,使用所有可用的核。例如,从一个图片列表中生成缩略图。使用QtConcurrent的filter()函数来选择容器元素,然后用map()函数将一个操作应用到每个元素上。用filterReduced()函数和mappedreduced()函数打包成一个单一的结果输出。
One call / Permanet在一个纯QML应用程序中执行一长段的计算工作,并当结果出来时更新GUI。将执行计算的代码放在一个.js的脚本中,并将其附加到WorkerScript实例中,在一个新的线程中调用sendMessage()来开启计算。也要让这个脚本调用WorkerScript::sendMessage(),然后将结果返回到GUI线程中。在onMessage中处理结果并更新GUI。
Permanet有一个生存在另一个线程中的对象,这个对象可以执行不同的请求任务或者可以接收新数据。子类化一个QObject来创建一个工人,实例化这个工人对象和一个QThread。将工人移到创建的新线程中。使用连接起来的信号-槽向工人发送命令或者数据。
Permanet在另一个线程中重复的执行一个开销很大的操作,该线程不需要接收信号或事件。在重新实现的QThread::run()中直接写一个无限循环。没有事件循环的情况下开启这个线程。让线程发送携带数据的信号给GUI线程。









  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值