Qt 多线程知识点整理

本书总结来自:OpenCV3和Qt5计算机视觉应用开发

进程和线程的区别

(1)进程与单个程序类似,可以由操作系统直接执行
(2)线程是一个进程的子集,一个进程可以包含多个线程
(3)通常情况下,不同的进程彼此是无关的,而不同的线程共享内存和资源(注意可以通过操作系统提供的手段实现彼此间的交互)
应用举例:
(1)进度条,工作剩余的百分比,完成搜索所用的耗时等
(2)计算机视觉中的视频或者摄像机处理,需要及时的正确读取处理显示视频
最好的办法就是将实际的任务跟GUI分解到不同的任务中。

1.Qt中的多线程

一般来说Qt有两种不同的多线程处理。

  1. 基于QThread的低级方式,该方法提供了很强的灵活性,并有效实现了对多线程的控制,但是需要更过的代码及耐心才能使线程准确无误的工作等,
  2. 基于QTConcurrent命名空间(或者Qt的并发框架),这是一个在应用程序中创建并运行多个任务的高级方法。
常用类 说明
QThread 该类是Qt框架中所有线程的基类,可以从QTread派生子类以创建新的线程,需要重新run,或者创建QThread的新实例,并通过调用moveToThread 函数将任何Qt对象(QObject)移动到新线程中
QThreadPool 用于管理线程,并且允许重用已有线程以实现新的功能,从而降低线程创建的成本,每个Qt应用程序都包含一个全局QThreadPool实例,可通过使用QThreadPool::globalInstance()静态函数来访问该实例。QThreadPool类可与QRunnable类的实例一同使用,以控制、管理并回收Qt应用程序中的runnable对象。
QRunable 可提供创建线程的另一种方法,这是Qt中所有runnable对象的基类,与QThread不同,Qrunnable不是QObject的子类,他可用作一段需要执行的代码的接口。你需要编写一个派生自QRunnablede 类并重写其中的纯虚函数run(),以便能够使用Qrunnable.因此,QRunnable的实例是由QThreadPool类管理的。
QMutex、QMutexLocker、QSemaphore、QWsitCondition、QReadLocker、QWriteLocke 这些类主要用于处理线程问的同步任务,根据情况不同,这些类可用来避免各种问题,比如线程相互覆盖计算结果,线程视图读取或写入一次只能处理一个线程的设备以及发一些类似的问题,在创建多线程应用程序时,经常需要手动处理类似的问题。
QtConcurrent 是一个命名空间,可用于使用高级API创建多线程应用程序。能够使多线程应用程序的编写更容易,无须处理互斥锁、信号量以及线程同步问题。
Qfuture、QfutureWatcher、QFututelterator和QFutureSynchronizer 这些类与QtConcurrent,命名空间共同使用,可以处理多线程及异步操作结果。

2.利用QThread实现低级多线程

Qt中所有对UI的操作都要放到主线程中,要不然操作UI就会造成卡顿现在

2.1 子类话QThread(重写run函数)

继承public:QThread()并重写run函数,在调用时调用start函数启动线程
start: 可用于启动一个尚未启动的线程,该函数通过调用所实现的run函数来启动执行,并且可以传以下值传递给start函数以实现对线程优先级的控制。

Qt 多线程知识点整理_第1张图片
terminate: 这个函数只在极端情况下使用,不推荐使用,他会强制终止线程
setTerminationEnable: 可用于允许或禁止 terminate 函数
wait: 该函数可用于阻塞线程(强制等待),直至线程完成或者达到超时值(以毫秒为单位)。
requestInterruption和isRequestInterrupted: 可用于设置和获取中断请求状态,通过恰当的使用这些函数,可确保在一个可永久运行的进程中间安全的停止线程。
isRunning 和 isFinished: 可用于请求线程的执行状态。
此外QTread还有一些其他的处理多线程的函数例如quit、exit、ideaThreadCount等。

2.2 move To Thread 函数

使用说明:首先创建一个继承自QObject的类,然后在使用类中new,使用关键代码如下:

VideoProcess *process = new VideoProcess();
process->moveToThread(new QThread(this));

process->thread()->start()    //启动函数

注意:使用moveToThread时,有父对象的对象不能移动新线程中,对象且为指针
结束代码:

process->stopProcess()    //自定义停止函数
process->thread()->quit()   //终止函数
process->thread()->wait()    //直至线程结束或达到超时毫秒

注意:调用quit()时,其对象不应含有任何正在运行的循环或者挂起的命令,否则会有很大问题。

3. 线程同步工具

线程并行会导致资源竞争,线程同步工具会解决这些冲突。

3.1互斥锁

互斥锁只是一种保护和防止多个线程同时访问统一对象实例的方法,Qt提供了名为QMutex的类来处理访问序列化问题。

QMetex me;
me.lock();
//代码段
me.unlock();

这种模式只能保证一个线程正在访问对象,但是每次都需要加锁解锁。因此可以使用QMutexLocker

QMetex me;
forver{
QMutexLocker locker(&me);
}

3.2读写锁

尽管互斥锁功能强大,但是它缺少某些功能,如不同类型的锁,尽管序列化访问非常有用,但难以有效的应用于读写序列化之类的问题,这类问题主要依赖于两种不同类型的锁:读、写。举个例子来说明:希望不同的线程能够同时读取对象但是希望保证只有一个线程可以在任何给定时间修改或写入该对象。对于这种情况可以使用读写锁、这基本上是增强的互斥锁,Qt框架提供了QreadWriteLock类,可以像QMetux类一样使用,区别在于它提供了一个用于读取的锁函数(lockForRead)和另外一个用于写入的锁函数(lockForWrite).

  • 如果在访问中调用lockForRead函数,则其他线程仍然可以调用lockForRead并为了读取而访问敏感对象,(所谓敏感对象指的是正在使用锁的对象)
  • 此外,如果在线程中调用lockForRead函数,则任何调用lockForWrite的线程都将被阻塞,直至该线程调用解锁(unlock)函数为止。
  • 如果在线程中调用lockForWrite函数,则所有其他线程(无论是读还是写)都将被阻塞,直至该线程调用解锁(unlock)函数
  • 如果在线程中调用lockForWrite函数,而前一线程已经有一个读取锁,那么所有用lockForRead的新线程都必须等待需要写入锁的线程,因此需要lockForWrite的线程将被赋予更高的优先级。代码
forever{
lock.lockForRead();
rad()
lock.unlock();
}
forever{
lock.lockForWrite();
rad()
lock.unlock();

或者可以类似QMutexLocker

forever{
QReadLocker locker(&lock);
rad()
forever{
QWriteLocker locker(&lock);
rad()

3.3信号量

有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备可能有非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成锁定和解锁操作,而且可以跟踪可用资源的数量。
Qt 框架提供了一个Qsemaphore的类,他可以在多线程编程中使用信号量,由于信号量用于根据可用资源的数量进行线程同步,因此函数名(意为旗语)也更适合于此用途,而不像锁函数和解锁函数,在Qsemaphore类中可用函数如下:

  • acquire :可用来获得特定数量的所需资源,如果没有足够的资源,那么线程将被阻塞直至等到有足够的资源。
  • release :可用来释放已经使用并且不在需要的特定数量的资源
  • available :可用来获取可用资源的数量,当希望线程执行另一个任务而不是等待资源时,将用到该函数。
    举例:假设有100兆字节可用内存空间供所有线程使用,每一个线程都需要x兆字节用以执行相应任务,而对于不同的线程,x是不同的,假设使用在线程中处理的图像大小或任何其他方法计算x.对于我们当前的问题可以使用QSemaphore类保证我们的线程只访问不超出可用范围的内存空间。因此在程序中创建信号量。
QSemaphore memSem(100);

并且在每个线程中,在内存密集型进程之前和之后,将分别获得和释放所需的内存空间,像下面这样:

memSem.acquire(x);
process_image();   memory intensive process
memSem.release(x);

注意:在上述例子中,如果X在某个线程中大于100,那么在release函数调用(释放资源)的数量等于或大于acquire函数调用(获取资源)的数量之前,该线程不能或者相应的资源。这表示可以通过调用release函数来增加(或创建)可用资源的数量,而release函数释放的资源应大于获得资源的数量。

3.4等待条件

多线程编程中还可能出现另外一个常见的问题,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件,这是问题就会出现。这种情况下线程会很自然地使用互斥锁或者读写锁来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进入了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另一个线程唤醒。
Qt框架有一个名为QWaitCondition的类,可专门用于处理上述这类问题,可能需要等待某些条件的任意一个线程都可能用到这个类,举例:
假设有很多线程处理一个Mat类(图像)一个线程负责读这个图像(只在图像存在时)。在假设一个进程、程序或用户负责创建这个图像文件,那么这个图像文件可能暂时不可用,因为该图像是由多个线程使用的,所以需要一个互斥锁,保证一次只有一个线程方位该图像,但是如果该图像仍然不存在,读取者线程可能需要继续等待,因此:

forever
{
mutex.lock();
imageExistcond.wait(&mutex);
read_image();
mutex.unlock();
}

注意:在上面这个例子中,mutex是QMetux类型,而imageExistsCond是QWaitCondition类型、前面代码说明锁定互斥锁开始读取图像,但是如果你必须等待图像存在,那么释放该互斥锁,以便其他线程可以继续工作,这需要另外一个线程负责读者线程。因此:

forever{
if(QFile::exists("image.jpg"))
    imageExistsCond.wakeAll();
}

这个线程还是检查图像文件是否一直存在,如果存在,他会试图唤醒等待这个等待条件的所有线程。还可以使用wakeOne函数而不是wakeAll,wakeOne只是试图从多有等待条件的线程中,随机地唤醒一个线程。万一希望条件满足时只有一个线程开始工作,则可以采用这种方式。

4. 基于QtConcurrent的高级多线程

主要的目的用于处理不需要线程同步的工具下创建多线程程序,其实现的方法是使用任何平台的最优线程来处理数据列表,以下函数通常使用高级API,用来处理多线程

filter 滤波列表
filtered 与filter函数的工作方式相同,区别是filtered返回的滤波后的列表,而不是更新输入列表
filteredReduced 与filtered函数的工作方式类似,但是他还应用与第二个函数将每一项传递给滤波器
map 可用于将一个特定的函数应用到一个列表中的所有项(使用最优或者定义一个自定义的线程数)另外,map函数还需要一个列表和一个函数
mapped 与map的工作方式相同,只是mapped返回了结果列表,而不是更新输入列表
mappedReduced 与map的工作方式雷瑟,但是他还将第二个函数应用到第一个mapping函数之后的每一项
run 用于在一个单独线程中轻松执行这个函数

对于函数的返回值,实际上指的是异步计算的结果,Qtconcurrent 在不同的线程中启动所有的计算,都会立刻返回给调用放,并且只有在计算完成之后,结果才可用,这是通过使用future的变量完成的,或者是在Qt框架中实现QFuture及附属类来完成。
QFuture类可用于检索QtConcurrent命名空间中的某个函数启动的计算结果,通过暂停、恢复等诸如此类的方法对计算过程进行控制;监控计算的进度。为了能够使用Qt信号和槽已实现对QFuture类更加灵活的控制,可以使用名为QFutureWatcher的类,它包含的一些信号和槽,通过一些进度条之类的控件对计算进行监控。


你可能感兴趣的:(Qt,开发语言)