线程多可以有解决在不冻结一个应用程序用户界面的情况下执行一个耗时的操作问题。
1.一般子类化QThread,然后重载run()函数。操作:使用start()启动线程后,run函数在线程启动的时候自动执行(只执行一次),isRunning()判断该线程是否正在运行,
2.也可以新建一个线程,不子类化QThread,使用QObject::moveToThread(QThread * targetThread),将当前类的一些耗时操作放到线程里面。
3.线程启动停止时,分发送信号。也可以使用isFinished()和isRunning()来查询线程的状态。使用wait()阻塞调用线程,直到另一个线程完成执行(或经过指定的时间)。从Qt 4.8开始,通过将finished()信号连接到QObject::deleteLater(),可以释放位于刚刚结束线程中的对象。QThread还提供了与平台无关的静态睡眠函数:sleep()、msleep()和usleep()分别允许完整的秒、毫秒和微秒分辨率。静态函数currentThreadId()和currentThread()返回当前执行线程的标识符。前者返回线程的平台特定ID;后者返回一个QThread指针。
同步线程的意思就是,在需要的时候把某一个线程停止,等待其他线程结束再执行。QT的线程和MFC的线程不太一样,QT的线程是同时执行的。比如,两个线程同时访问全局变量,避免同时访问产生不良后果。
1.QMutex类提供线程之间的访问序列化。
QMutex的目的是保护对象、数据结构或代码段,以便一次只有一个线程可以访问它(这类似于Java synchronized关键字)。通常最好将互斥锁与QMutexLocker一起使用,因为这样可以很容易地确保一致地执行锁定和解锁。
2.QMutexLocker类是一个方便的类,它简化了互斥锁的锁定和解锁。在复杂的函数和语句或异常处理代码中锁定和解锁q互斥锁是容易出错的,并且很难调试。在这种情况下,可以使用QMutexLocker来确保互斥锁的状态总是定义良好的。应该在需要锁定QMutex的函数中创建QMutexLocker。创建QMutexLocker时锁定互斥锁。您可以使用unlock()和relock()对互斥锁进行解锁和重锁。如果锁定,当QMutexLocker被销毁时,互斥锁将被解锁。
3.QReadLocker类是一个方便的类,它简化了读写锁的锁定和解锁。QReadLocker(和QWriteLocker)的目的是简化QReadWriteLock锁定和解锁。锁定和解锁语句或异常处理代码中的锁定和解锁语句很容易出错,而且很难调试。在这种情况下,可以使用QReadLocker来确保锁的状态始终定义良好。
4,.QReadWriteLock类提供读写锁。读写锁是一种同步工具,用于保护可用于读写的资源。如果您希望允许多个线程同时进行只读访问,那么这种类型的锁非常有用,但是只要一个线程想要写入资源,就必须阻塞所有其他线程,直到写入完成。
5.QSemaphore提供了一个通用计数信号量。负责保护一定数量的共同访问的资源。
6. QWaitCondition用来唤醒其它线程的环境变量,QWaitCondition允许一个线程告诉其他线程已经满足了某种条件。一个或多个线程可以阻塞等待QWaitCondition来使用wakeOne()或wakeAll()设置条件。使用wakeOne()唤醒随机选择的一个条件,或者使用wakeAll()唤醒所有条件。
7.
8.
9.
QtConcurrent::map()、QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 函数对一个序列中(例如:QList、QVector)的项目并行地进行计算。QtConcurrent::map() 就地修改一个序列,QtConcurrent::mapped() 返回一个包含修改内容的新序列,QtConcurrent::mappedReduced() 返回一个单一的结果。
这些函数是 Qt之Concurrent框架 的一部分。
10.QAtomicInt类提供了与平台无关的整数原子操作。
有关指针上的原子操作,请参见QAtomicPointer类。
原子操作是一种不间断完成的复杂操作。QAtomicInt类为整数提供原子引用计数、测试和设置、获取和存储以及获取和添加。
QAtomicPointer类为指针提供原子测试和设置、获取和存储以及获取和添加。
11.QFuture 类代表一个异步计算的结果。QFutureSynchronizer类是一个方便的类,它简化了QFuture同步。QFutureWatcher类允许使用信号和插槽监视QFuture。
12.QRunnable类是所有runnable对象的基类。
QRunnable类是表示需要执行的任务或代码段的接口,由run()函数的重新实现表示。
13.QThreadPool类管理QThreads的集合。
QThreadPool管理和回收各个QThread对象,以帮助降低使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用globalInstance()访问该对象。
14.QThreadStorage类提供每个线程的数据存储。
QThreadStorage是一个模板类,它提供每个线程的数据存储。
15.QWriteLocker类是一个方便的类,它简化了用于写访问的读写锁的锁定和解锁。
16.QtConcurrent命名空间提供了高级api,可以在不使用低级线程基元的情况下编写多线程程序。
参见线程文档中的Qt Concurrent一章。
QTread使用exit()和quit()启动事件循环,使得线程可以使用需要事件循环的非GUI类(如QTimer,QTcpSocket、QProcess)。没有事件循环无法发送QTimer的信号等等。
线程就两种使用方法,子类化QThread重载run函数和moveToThread,其它办法是没办法让操作放到一个单独的线程里边的,(比如子类化线程后,在子类化的类构造函数里边关联信号槽,槽的执行时不会在这个子类化的类中执行,而是在创建它的线程中执行)
使用qApp->thread()可以获得主线程,currentThread()可以获得当前执行线程的指针,currentThreadId ()可以获得一个编号。(得到编号这个尽量少用,官方文档说不要在windows下用于数值比较)
1. 一个对象的线程就是创建该对象时的线程,而不论该对象的定义是保存在那个线程中。如果在一个子类化QThread的构造函数中,创建一个对象,那么这个对象所在的线程是创建这个子类化对象的线程。如果在一个子类化QThread的run函数中,创建一个对象,那么这个对象所在的线程是这个子类化对象。可以通过调用QObject::thread()
可以查询一个QObject
的线程依附性。
如果在run()里面new一个对象,那么内存不太容易管理,会内存泄漏。但是可以通过在run里面创建一个局部变量,然后用类的成员指针指向它,就可以解决这个问题。
2. QObject的connect函数有几种连接方式,
a) DirectConnection,信号发送后槽函数立即执行,由sender的所在线程执行;
b) QueuedConnection,信号发送后返回,相关槽函数由receiver所在的线程在返回到事件循环后执行;
c) 默认使用的是Qt::AutoConnection,当sender和receiver在同一个线程内时,采用DirectConnection的方式,当sender和receiver在不同的线程时,采用QueuedConnection的方式。
要使得子类化线程的成员和它的信号关联的槽函数均在子类化线程的成员中执行,成员必须在run函数中创建(可以使用成员指针指向在run函数创建的对象),且信号和槽的关联必须采用DirectConnection关联方式,然后在run使用exec函数进入事件循环(注意不是exit!!!exit是退出事件循环!!!)。
3.根据快速入门那本书461页上面写的,事件驱动模块(比如定时器和网络模块)只能在单一线程中使用,如果通过信号槽跨线程使用,不一定能用,可能会报以下的错误。
4.当使用exit函数退出事件循环的时候,里面的事件驱动模块全部关闭,再次start此线程的时候,里面的事件驱动模块全部重新开启。应该是exit只是退出事件循环,但是线程中创建的对象仍然存在,再次start事件循环又开启。
跨线程的信号槽是比较容易出错的,遇到的时候多调试,避免出错。QUdpSocket在线程中使用有点特别,需要注意。
QT有实现多线程应用程序的四种不同方法。
QThread是Qt中所有线程控件的基础,每个QThread实例表示和控制一个线程。
QThread可以直接实例化,也可以子类化。实例化QThread提供了一个并行事件循环,允许在辅助线程中调用QObject插槽。子类化QThread允许应用程序在启动事件循环之前初始化新线程,或者在没有事件循环的情况下运行并行代码。
频繁地创建和销毁线程可能代价高昂。为了减少这种开销,可以对新任务重用现有线程。QThreadPool是可重用QThreads的集合。要在QThreadPool的一个线程中运行代码,请重新实现QRunnable::run()并实例化子类化的QRunnable。使用QThreadPool::start()将QRunnable放入QThreadPool的运行队列中。当线程可用时,QRunnable::run()中的代码将在该线程中执行。每个Qt应用程序都有一个全局线程池,可以通过QThreadPool::globalInstance()访问该线程池。这个全局线程池根据CPU中的内核数量自动维护最佳线程数量。但是,可以显式地创建和管理一个单独的QThreadPool。
Qt并发模块提供高级功能,处理一些常见的并行计算模式:map、filter和reduce。可以在不使用低级线程的情况下编写多线程程序。与使用QThread和QRunnable不同,这些函数从不需要使用底层线程原语,如互斥或信号量。相反,它们返回一个QFuture对象,当函数准备好时,该对象可用于检索函数的结果。QFuture还可以用来查询计算进度和暂停/恢复/取消计算。为了方便起见,QFutureWatcher允许通过信号和插槽与QFutures进行交互。Qt Concurrent的map、filter和reduce算法自动将计算分布到所有可用的处理器内核上,因此,今天编写的应用程序在以后部署到具有更多内核的系统时将继续扩展。
WorkerScript QML类型允许JavaScript代码与GUI线程并行运行。
四种方法对比
QT组手上有不同需求的解决方案推荐。
因此,线程安全的函数总是可重入的,但可重入的函数并不总是线程安全的。
通过扩展,如果一个类的成员函数可以从多个线程安全地调用,那么这个类就是可重入的,只要每个线程使用该类的不同实例。如果可以从多个线程安全地调用该类的成员函数,即使所有线程都使用该类的同一个实例,该类也是线程安全的。
Qt类只有在打算由多个线程使用时才作为线程安全记录下来。如果函数没有被标记为线程安全的或可重入的,则不应该从不同的线程使用它。如果一个类没有被标记为线程安全的或可重入的,那么不应该从不同的线程访问该类的特定实例。
C++类是可重入的,可以被多个线程调用,但是其结果未知。使用QMutex类提供线程之间的访问序列化,使得线程依次访问。
许多Qt类是可重入的,但它们不是线程安全的,因为使它们线程安全会导致重复锁定和解锁q互斥锁的额外开销。例如,QString是可重入的,但不是线程安全的。您可以同时从多个线程安全地访问不同的QString实例,但是不能同时从多个线程安全地访问相同的QString实例(除非您使用QMutex保护自己的访问)。
QThread继承QObject。它发出信号来指示线程已启动或已完成执行,并提供一些插槽。QObject是可重入的。它的大多数非gui子类(如QTimer、QTcpSocket、QUdpSocket和QProcess)也是可重入的,这使得同时从多个线程使用这些类成为可能。注意,这些类被设计为在一个线程内创建和使用;在一个线程中创建一个对象并从另一个线程调用它的函数并不保证能够工作。有三个限制因素需要注意:
1.QObject的子对象必须始终在创建父对象的线程中创建。这意味着,您永远不应该将QThread对象(这个)作为在线程中创建的对象的父对象传递(因为QThread对象本身是在另一个线程中创建的)。
2.事件驱动对象只能在一个线程中使用。具体来说,这适用于计时器机制和网络模块。例如,您不能启动计时器或连接非对象线程中的套接字。
3.必须确保在删除QThread之前删除线程中创建的所有对象。这可以通过在run()实现中在堆栈上创建对象轻松实现。
每个线程都可以有自己的事件循环。初始线程使用QCoreApplication::exec()或对于单对话框GUI应用程序,有时使用QDialog::exec()启动其事件循环。其他线程可以使用QThread::exec()启动事件循环。与QCoreApplication类似,QThread提供了exit(int)函数和quit()插槽。
线程中的事件循环使线程能够使用某些非gui Qt类,这些类需要事件循环的存在(如QTimer、QTcpSocket和QProcess)。它还可以将任何线程的信号连接到特定线程的插槽。下面的信号和跨线程插槽一节将更详细地解释这一点。
支持线程的QT模块:SQL 模块,Painting,富文本处理,SVG模块,隐式共享类
QObject及其所有子类都不是线程安全的。这包括整个事件交付系统。重要的是要记住,当您从另一个线程访问对象时,事件循环可能正在向QObject子类交付事件。
如果您正在调用一个不在当前线程中的QObject子类上的函数,并且该对象可能接收事件,则必须使用互斥锁保护对QObject子类内部数据的所有访问;否则,您可能会遇到崩溃或其他不希望发生的行为。
1.在线程中无法使用任何界面部件类。
2.C++类是可重入的,可以被多个线程调用,但是其结果未知。使用QMutex类提供线程之间的访问序列化,使得线程依次访问。
3.,QThread实例位于实例化它的旧线程中,而不是位于调用run()的新线程中。新的信号槽不要实现到子类的 QThread。当子类化QThread时,请记住构造函数在旧线程中执行,而run()在新线程中执行。如果从两个函数访问一个成员变量,则从两个不同的线程访问该变量。检查这样做是否安全。
分形算法数据量大,所以放到单独的一个线程里面。界面支持鼠标滚动、按键支持。QWaitCondition的使用:wait释放锁住互斥锁,并在等待条件下等待。
QSemaphore比QMutex更高级一点,可以使得两个线程同时访问缓冲区的不同位置,而QMutex不允许多个线程同时访问一个变量。两个基本操作,获取和释放。.如果资源还未释放,acquire()将会阻塞。信号量的一个典型应用程序是控制对生产者线程和消费者线程共享的循环缓冲区的访问。信号量的一个非计算示例是在餐馆用餐。一个信号量初始化为餐厅中椅子的数量。当人们到达时,他们想要一个座位。由于座位已满,空座()减少。当人们离开时,available()会增加,允许更多的人进入。如果一个10人的团体想要入座,但只有9个座位,那10个人就会等待,而一个4人的团体就会坐定(把可用的座位让给5人,让10人的团体等待更长时间)。
和Semaphores例子实现一样的功能。使用QSemaphore类要更简洁一些。QWaitCondition将会使得使用其wait函数的线程等待,wakeAll函数将会唤醒所有正在等待的线程。这个例子采用QMutex和QWaitCondition,生产者未被消费者读取的字节数超过8192时,生产者线程会等待,生产者未被消费者读取的字节数为0时,消费者线程会等待,从而实现生产者不会比消费者多8192字节,消费者不会读取生产者未生产字节。QMutex的作用在于lock()与unlock()函数之间的变量,在一个线程访问的时候,其它线程不允许访问。这个例子的四次使用QMutex,作用是访问时保证统计被读取的字节数不被两个线程同时访问,从而保证数据准确。
使用Qt的阻塞网络API通常会导致更简单的代码,但是由于其阻塞行为,它应该只在非gui线程中使用,以防止用户界面冻结。使用线程和QThread并不一定会给应用程序增加不可管理的复杂性。
FortuneThread是负责TCP通信的线程类,界面会向线程调用其requestNewFortune函数,requestNewFortune使用QMutexLocker锁定资源,QMutexLocker变量生命周期结束的时候,锁定就结束。线程在不在运行的时候,将启动线程,在运行的时候,将会唤醒挂起的线程。
在run函数中,首先会使用QMutex锁定资源防止多次调用,serverName、serverPort值改变引起未知后果。然后进入一个循环,使用waitForConnected获取传输的字节,waitForReadyRead获取传输的数据,这两个函数都会挂起线程。得到传输的数据之后,使用QMutex锁定资源和QWaitCondition挂起程序。等待下一次传输数据开始。
在FortuneThread的析构函数中,会锁定资源并唤醒线程,线程被销毁。
这个例程的功能为,打开一张图片,通过线程将规定大小的区域块按照一定时间间隔去读取,每次读取一定大小的矩形内的每一个像素的RGB,最后形成一个图片块,让QPainter绘制,最后大致形成一个和原图一样的图片。需要对线程进行msleep睡眠的原因是,如果不休眠,QPainter的绘制负担过大,程序会卡顿,而线程类本身运行却不会卡顿。停止的时候和在析构函数中对线程成员m_abort使用QMutex锁定资源锁定。
QtConcurrent Map示例展示了如何使用异步QtConcurrent API加载和缩放图像集合。QFuture 类代表一个异步计算的结果,QFuture提供了一个java风格的迭代器(qfutuator)和一个stl风格的迭代器(QFuture::const_iterator)。使用这些迭代器是将来访问结果的另一种方法。一般与迭代器配合使用,使用QFuture
这个例子有点疑问,缩放了20张图片,但是只打印了17个信息。这个是同步缩放。有可能是同步使用了不同的线程,但是有些线程处理了两张图片。
此例使用QFutureWatcher类来监视长时间运行的操作的进度。和Image Scaling类似,只不过容器换成了QVector。还有就是使用了progressRangeChanged(int,int)信号得到QFutureWatcher的检测容器的最大值和最小值,信号progressValueChanged(int)得到当前完成操作的容器项索引。
这个例子比较了使用单线程和多线程计算单词数的时间消耗对比,mappedReduced可以使得计算一个容器的每一项返回到另一个函数里面。QFuture
mapFunction会使用几个线程,但是reduceFunction只使用了一两个线程。
这个例子使用QFuture
.1.当前线程创建数据库对象和查询对象只能在当前线程中使用,不能跨线程使用
这里说的是一个线程创建的 QSqlDatabase 对象和 查出来的 QSqlQuery 对象只能在当前线程中使用。一个数据库连接本身比如一个连接的名称是可以在不同线程中使用的。默认连接名称是 “qt_sql_default_connection”