Qt 线程基础(QThread、QtConcurrent等)

转载自:http://blog.csdn.net/dbzhang800/article/details/6554104

昨晚看Qt的Manual,突然发现下一个版本的Qt中(Qt4.7.4、Qt4.8等)增加了一个特赞的介绍多线程的文章 :

注意:

  • 该链接以后会失效,但是 到时候你直接看Qt自带Manual就行了
  • 本文不是严格的翻译 dbzhang800 2011.06.18

使用线程

基本上有种使用线程的场合:

  • 通过利用处理器的多个核使处理速度更快。
  • 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程。

何时使用其他技术替代线程

开发人员使用线程时需要非常小心。启动线程是很容易的,但确保所有共享数据保持一致很难。遇到问题往往很难解决,这是由于在一段时间内它可能只出现一次或只在特定的硬件配置下出现。在创建线程来解决某些问题之前,应该考虑一些替代的技术

替代技术

注解

QEventLoop::processEvents()

在一个耗时的计算操作中反复调用QEventLoop::processEvents() 可以防止界面的假死。尽管如此,这个方案可伸缩性并不太好,因为该函数可能会被调用地过于频繁或者不够频繁。

QTimer

后台处理操作有时可以方便地使用Timer安排在一个在未来的某一时刻执行的槽中来完成。在没有其他事件需要处理时,时间隔为0的定时器超时事件被相应

QSocketNotifier 
QNetworkAccessManager 
QIODevice::readyRead()

这是一个替代技术,替代有一个或多个线程在慢速网络执行阻塞读的情况。只要响应部分的计算可以快速执行,这种设计比在线程中实现的同步等待更好。与线程相比这种设计更不容易出错且更节能(energy efficient)。在许多情况下也有性能优势。

一般情况下,建议只使用安全和经过测试的方案而避免引入特设线程的概念。QtConcurrent 提供了一个将任务分发到处理器所有的核的易用接口。线程代码完全被隐藏在 QtConcurrent 框架下,所以你不必考虑细节。尽管如此,QtConcurrent 不能用于线程运行时需要通信的情况,而且它也不应该被用来处理阻塞操作。

应该使用 Qt 线程的哪种技术?

有时候,你需要的不仅仅是在另一线程的上下文中运行一个函数。您可能需要有一个生存在另一个线程中的对象来为GUI线程提供服务。也许你想在另一个始终运行的线程中来轮询硬件端口并在有关注的事情发生时发送信号到GUI线程。Qt为开发多线程应用程序提供了多种不同的解决方案。解决方案的选择依赖于新线程的目的以及线程的生命周期。

生命周期

开发任务

解决方案

一次调用

在另一个线程中运行一个函数,函数完成时退出线程

编写函数,使用QtConcurrent::run 运行它

派生QRunnable,使用QThreadPool::globalInstance()->start() 运行它

派生QThread,重新实现QThread::run() ,使用QThread::start() 运行它

一次调用

需要操作一个容器中所有的项。使用处理器所有可用的核心。一个常见的例子是从图像列表生成缩略图。

QtConcurrent 提供了map()函你数来将操作应用到容器中的每一个元素,提供了fitler()函数来选择容器元素,以及指定reduce函数作为选项来组合剩余元素。

一次调用

一个耗时运行的操作需要放入另一个线程。在处理过程中,状态信息需要发送会GUI线程。

使用QThread,重新实现run函数并根据需要发送信号。使用信号槽的queued连接方式将信号连接到GUI线程的槽函数。

持久运行

生存在另一个线程中的对象,根据要求需要执行不同的任务。这意味着工作线程需要双向的通讯。

派生一个QObject对象并实现需要的信号和槽,将对象移动到一个运行有事件循环的线程中并通过queued方式连接的信号槽进行通讯。

持久运行

生存在另一个线程中的对象,执行诸如轮询端口等重复的任务并与GUI线程通讯。

同上,但是在工作线程中使用一个定时器来轮询。尽管如此,处理轮询的最好的解决方案是彻底避免它。有时QSocketNotifer是一个替代。

Qt线程基础

QThread是一个非常便利的跨平台的对平台原生线程的抽象。启动一个线程是很简单的。让我们看一个简短的代码:生成一个在线程内输出"hello"并退出的线程。

 // hellothread/hellothread.h
class HelloThread : public QThread
{
Q_OBJECT
private:
void run();
};

我们从QThread派生出一个类,并重新实现run方法。

 // hellothread/hellothread.cpp
void HelloThread::run()
{
qDebug() << "hello from worker thread " << thread()->currentThreadId();
}

run方法中包含将在另一个线程中运行的代码。在本例中,一个包含线程ID的消息被打印出来。  QThread::start() 将在另一个线程中被调用。

 int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
HelloThread thread;
thread.start();
qDebug() << "hello from GUI thread " << app.thread()->currentThreadId();
thread.wait(); // do not exit before the thread is completed!
return 0;
}

QObject与线程

QObject有线程关联(thread affinity)[如何翻译?关联?依附性?dbzhang800 20110618],换句话说,它生存于一个特定的线程。这意味着,在创建时QObject保存了到当前线程的指针。当事件使用postEvent()被派发时,这个信息变得很有用。事件被放置到相应线程的事件循环中。如果QObject所依附的线程没有事件循环,该事件将永远不会被传递。

要启动事件循环,必须在run()内调用exec()。线程关联可以通过moveToThread()来更改。

如上所述,当从其他线程调用对象的方法时开发人员必须始终保持谨慎。线程关联不会改变这种状况。 Qt文档中将一些方法标记为线程安全。postEvent()就是一个值得注意的例子。一个线程安全的方法可以同时在不同的线程被调用。

通常情况下并不会并发访问的一些方法,在其他线程调用对象的非线程安全的方法在出现造成意想不到行为的并发访问前数千次的访问可能都是工作正常的。编写测试代码不能完全确保线程的正确性,但它仍然是重要的。在Linux上,Valgrind和Helgrind有助于检测线程错误。

QThread的内部结构非常有趣:

  • QThread并不生存于执行run()的新线程内。它生存于旧线程中。
  • QThread的大多数成员方法是线程的控制接口,并设计成从旧线程中被调用。不要使用moveToThread()将该接口移动到新创建的线程中;调用moveToThread(this)被视为不好的实践。
  • exec()和静态方法usleep()、msleep()、sleep()要在新创建的线程中调用。
  • QThread子类中定义的其他成员可在两个线程中访问。开发人员负责访问的控制。一个典型的策略是在start()被调用前设置成员变量。一旦工作线程开始运行,主线程不应该操作其他成员。当工作线程终止后,主线程可以再次访问其他成员。这是一个在线程开始前传递参数并在结束后收集结果的便捷的策略。

QObject必须始终和parent在同一个线程。对于在run()中生成的对象这儿有一个惊人的后果:

 void HelloThread::run()
{
QObject *object1 = new QObject(this); //error, parent must be in the same thread
QObject object2; // OK
QSharedPointer <QObject> object3(new QObject); // OK
}

使用互斥量保护数据的完整

互斥量是一个拥有lock()和unlock()方法并记住它是否已被锁定的对象。互斥量被设计为从多个线程调用。如果信号量未被锁定lock()将立即返回。下一次从另一个线程调用会发现该信号量处于锁定状态,然后lock()会阻塞线程直到其他线程调用unlock()。此功能可以确保代码段将在同一时间只能由一个线程执行。

使用事件循环防止数据破坏

Qt的事件循环对线程间的通信是一个非常有价值的工具。每个线程都可以有它自己的事件循环。在另一个线程中调用一个槽的一个安全的方法是将调用放置到另一个线程的事件循环中。这可以确保目标对象调用另一个的成员函数之前可以完成当前正在运行的成员函数。

那么,如何才能把一个成员调用放于一个事件循环中? Qt的有两种方法来做这个。一种方法是通过queued信号槽连接;另一种是使用QCoreApplication::postEvent()派发一个事件。queued的信号槽连接是异步执行的信号槽连接。内部实现是基于posted的事件。信号的参数放入事件循环后信号函数的调用将立即返回。

连接的槽函数何时被执行依赖于事件循环其他的其他操作。

通过事件循环通信消除了我们使用互斥量时所面临的死锁问题。这就是我们为什么推荐使用事件循环,而不是使用互斥量锁定对象的原因。

处理异步执行

一种获得一个工作线程的结果的方法是等待线程终止。在许多情况下,一个阻塞等待是不可接受的。阻塞等待的替代方法是异步的结果通过posted事件或者queued信号槽进行传递。由于操作的结果不会出现在源代码的下一行而是在位于源文件其他部分的一个槽中,这会产生一定的开销,因为,但在位于源文件中其他地方的槽。 Qt开发人员习惯于使用这种异步行为工作,因为它非常相似于GUI程序中使用的的事件驱动编程。

http://blog.csdn.net/wsh6759/article/details/7432337

Qt 线程基础(Thread Basics的翻译,线程的五种使用情况)的更多相关文章

  1. java线程基础巩固---创建并启动线程

    对于java的并发编程方面的东东,不管是面试还是实际工作开发都是非常重要的,而往往只要涉及到并发相关的东东有点让人觉得有点难,而实际工作中涉及到并发可能就是简单的用下同步块.上锁之类的一些简单的操作, ...

  2. 套间线程(apartment thread)模型和自由线程(free thread)模型互相创建的情况

     

  3. Qt 线程基础(QThread、QtConcurrent等)

    [-] 使用线程 何时使用其他技术替代线程 应该使用 Qt 线程的哪种技术 Qt线程基础 QObject与线程 使用互斥量保护数据的完整 使用事件循环防止数据破坏 处理异步执行 昨晚看Qt的Manua ...

  4. Qt 线程基础(QThread、QtConcurrent等) 2

    使用线程 基本上有种使用线程的场合: 通过利用处理器的多个核使处理速度更快. 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程. 何时使用其他技术替代线程 开发人员使用线 ...

  5. Qt 线程基础(QThread、QtConcurrent、QThreadPool等)

      使用线程 基本上有种使用线程的场合: 通过利用处理器的多个核使处理速度更快. 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程. 何时使用其他技术替代线程 开发人员使 ...

  6. java核心技术-多线程之线程基础

    说起线程,无法免俗首先要弄清楚的三个概念就是:进程.线程.协程.OK,那什么是进程,什么是线程,哪协程又是啥东西.进程:进程可以简单的理解为运行在操作系统中的程序,程序时静态代码,进程是动态运行着的代 ...

  7. 线程(Thread,ThreadPool)、Task、Parallel

    线程(Thread.ThreadPool) 线程的定义我想大家都有所了解,这里我就不再复述了.我这里主要介绍.NET Framework中的线程(Thread.ThreadPool). .NET Fr ...

  8. JAVA并发编程——守护线程(Daemon Thread)

    在Java中有两类线程:用户线程 (User Thread).守护线程 (Daemon Thread). 所谓守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称 ...

  9. Windows核心编程:第6章 线程基础

    Github https://github.com/gongluck/Windows-Core-Program.git //第6章 线程基础.cpp: 定义应用程序的入口点. // #include ...

随机推荐

  1. 多重背包的入门题目HDU1171,2191,2844.

    首先,什么叫多重背包呢? 大概意思就是:一个背包有V总容量,有N种物品,其价值分别为Val1,Val2--,Val3,体积对应的是Vol1,Vol2,--,Vol3,件数对应Num1,Num2--,N ...

  2. float浮动引起的ul高度崩溃与overflow的关系

        今天遇到的问题真的让人不得不吐槽,因为一个很小的问题,花费了半天的时间来才解决这个问题.一直认为自己对Html与Css了解应该算蛮不错的,但是今天遇到的事情让我不得不反省自己的学习心态上的错误 ...

  3. Hive 的 排序

    全排序:order by对全部所有的数据进行排序,在实现的时候是放到一个reduce中进行的,可以想象这样做效率是比较低的: 局部排序:sort by对数据进行分组,然后在组内进行排序,每个reduc ...

  4. linux下配置NFS服务器

    (声明:本文大部分文字摘自Linux NFS服务器的安装与配置) 一.NFS简介     NFS 是Network File System的缩写,即网络文件系统.一种使用于分散式文件系统的协定,由Su ...

  5. url、base64 编码规则

    UrlEncode 相关: URI所允许的字符分作保留与未保留. 保留字符是那些具有特殊含义的字符. 例如, 斜线字符用于URL (或者更一般的, URI)不同部分的分界符. 未保留字符没有这些特殊含 ...

  6. 写个自动安装JDK的shell脚本

    #!/bin/bash ################################################# # # INSTALL JDK AUTOMATICALLY # # auth ...

  7. seajs的spm使用

    压缩JS文件 只需要执行这个命令即可 spm build xxx.js 这时候你将得到一个压缩过的__build/xxx.js文件 合并JS文件 如果希望将JS文件中require的其他模块都合并到这 ...

  8. iOS便捷开发工具分享

    项目/代码优化工具 1.objec_dep,可以了解项目中各个类的关联信息,了解项目中无效文件,知道双向应用的文件. 下载地址: https://github.com/nst/objc_dep 2.b ...

  9. VMware Workstation 不可恢复错误: (vcpu-0)

  10. jQuery&HTML&CSS3实现垂直手风琴折叠菜单方法讲解

    在网页制作中我们常常需要折叠式的菜单,在折叠菜单中,手风琴特效的菜单是非常受欢迎,下面就讲解使用jQuery+HTML+CSS3实现垂直手风琴折叠菜单的方法. jQuery实现垂直手风琴折叠菜单示例代 ...