原文:http://blog.csdn.net/dzh0622/article/details/48012841
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
Feature | QThread | QRunnable 和 QThreadPool | QtConcurrent::run() | Qt Concurrent(Map, Fillter,Reduce) | WorkerScript |
API | C++ | C++ | C++ | C++ | QML |
可以指定线程优先级? | Yes | Yes | |||
线程可以运行事件循环 | Yes | ||||
线程可以接收信号更新过来的数据 | Yes(通过工人QObject接收) | Yes(通过WorkerScript接收) | |||
线程可以通过信号被控制? | Yes(通过Qthread接收) | Yes(通过QFutureWatcher接收) | |||
线程可以通过QFuture被监控? | 部分的 | Yes | |||
内嵌功能来暂停/恢复/取消? | Yes |
Example Use Cases
Lifetime of thread | Operation | Solution |
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线程。 |