QT多线程编程详解

本文详细介绍了QT的多线程编程,包括线程基础如GUI线程与工作线程、数据同步访问,重点讲解了QThread类的使用,如线程的创建、执行、退出以及线程同步方法如QMutex、QSemaphore等。同时探讨了线程与信号槽机制,以及线程与GUI组件的通信,提供了多种线程设计模式和通信方式。
摘要由CSDN通过智能技术生成

一、线程基础

1、GUI线程与工作线程

每个程序启动后拥有的第一个线程称为主线程,即GUI线程。QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在次线程,次线程即工作线程,主要负责处理GUI线程卸下的工作。

2、数据的同步访问

每个线程都有自己的栈,因此每个线程都要自己的调用历史和本地变量。线程共享相同的地址空间。

二、QT多线程简介

QT通过三种形式提供了对线程的支持,分别是平台无关的线程类、线程安全的事件投递、跨线程的信号-槽连接。

QT中线程类包含如下:

QThread提供了跨平台的多线程解决方案

QThreadStorage提供逐线程数据存储
QMutex提供相互排斥的锁,或互斥量
QMutexLocker是一个辅助类,自动对QMutex加锁与解锁
QReadWriterLock提供了一个可以同时读操作的锁
QReadLocker与QWriteLocker自动对QReadWriteLock加锁与解锁
QSemaphore提供了一个整型信号量,是互斥量的泛化
QWaitCondition提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。

三、QThread线程

1、QThread线程基础

QThread是Qt线程中有一个公共的抽象类,所有的线程类都是从QThread抽象类中派生的,需要实现QThread中的虚函数run(),通过start()函数来调用run函数。

void run()函数是线程体函数,用于定义线程的功能。

void start()函数是启动函数,用于将线程入口地址设置为run函数。

void terminate()函数用于强制结束线程,不保证数据完整性和资源释放。

QCoreApplication::exec()总是在主线程(执行main()的线程)中被调用,不能从一个QThread中调用。在GUI程序中,主线程也称为GUI线程,是唯一允许执行GUI相关操作的线程。另外,必须在创建一个QThread前创建QApplication(or QCoreApplication)对象。

当线程启动和结束时,QThread会发送信号started()和finished(),可以使用isFinished()和isRunning()来查询线程的状态。

从Qt4.8起,可以释放运行刚刚结束的线程对象,通过连接finished()信号到QObject::deleteLater()槽。
使用wait()来阻塞调用的线程,直到其它线程执行完毕(或者直到指定的时间过去)。

静态函数currentThreadId()和currentThread()返回标识当前正在执行的线程。前者返回线程的ID,后者返回一个线程指针。

要设置线程的名称,可以在启动线程之前调用setObjectName()。如果不调用setObjectName(),线程的名称将是线程对象的运行时类型(QThread子类的类名)。

2、线程的优先级

QThread线程总共有8个优先级

QThread::IdlePriority0scheduled only when no other threads are running.

QThread::LowestPriority1scheduled less often than LowPriority.

QThread::LowPriority2scheduled less often than NormalPriority.

QThread::NormalPriority3the default priority of the operating system.

QThread::HighPriority4scheduled more often than NormalPriority.

QThread::HighestPriority5scheduled more often than HighPriority.

QThread::TimeCriticalPriority6scheduled as often as possible.

QThread::InheritPriority7use the same priority as the creating thread. This is the default.

void setPriority(Priority priority)
设置正在运行线程的优先级。如果线程没有运行,此函数不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。优先级参数可以是QThread::Priority枚举除InheritPriortyd的任何值。

3、线程的创建

voidstart( Priority_priority_= InheritPriority )

启动线程执行,启动后会发出started()信号

4、线程的执行

int exec() [protected]
进入事件循环并等待直到调用exit(),返回值是通过调用exit()来获得,如果调用成功则返回0。

void run() [virtual protected]
线程的起点,在调用start()之后,新创建的线程就会调用run函数,默认实现调用exec(),大多数需要重新实现run函数,便于管理自己的线程。run函数返回时,线程的执行将结束。

5、线程的退出

void quit();

通知线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。

void exit( intreturnCode= 0 );

调用exit后,thread将退出event loop,并从exec返回,exec的返回值就是returnCode。通常returnCode=0表示成功,其他值表示失败。

void terminate();

结束线程,线程是否立即终止取决于操作系统。

线程被终止时,所有等待该线程Finished的线程都将被唤醒。

terminate是否调用取决于setTerminationEnabled( boolenabled= true )开关。

void requestInterruption()
请求线程的中断。请求是咨询意见并且取决于线程上运行的代码,来决定是否及如何执行这样的请求。此函数不停止线程上运行的任何事件循环,并且在任何情况下都不会终止它。

工程中线程退出的解决方案如下:

通过在线程类中增加标识变量volatile bool m_stop,通过m_stop变量的值判断run函数是否执行结束返回。

#ifndef WORKTHREAD_H
#define WORKTHREAD_H
#include <QThread>
#include <QDebug>
 
class WorkThread : public QThread
{
protected:
  //线程退出的标识量
  volatile bool m_stop;
  void run()
  {
    qDebug() << "run begin";
    while(!m_stop)
    {
        //task handling
        int* p = new int[1000];
        for(int i = 0; i < 1000; i++)
        {
            p[i] = i * i;
        }
        sleep(2);
        delete [] p;
    }
    qDebug() << "run end";
  }
public:
  WorkThread()
  {
    m_stop = false;
  }
  //线程退出的接口函数,用户使用
  void stop()
  {
    m_stop = true;
  }
};
 
#endif // WORKTHREAD_H
6、线程的等待

bool wait( unsigned longtime= ULONG_MAX )

线程将会被阻塞,等待time毫秒,如果线程退出,则wait会返回。Wait函数解决多线程在执行时序上的依赖。

void msleep( unsigned longmsecs)

void sleep( unsigned longsecs)

void usleep( unsigned longusecs)

sleep()、msleep()、usleep()允许秒,毫秒和微秒来区分,但在Qt5.0中被设为public。

一般情况下,wait()和sleep()函数应该不需要,因为Qt是一个事件驱动型框架。考虑监听finished()信号来取代wait(),使用QTimer来取代sleep()。

7、线程的状态

bool isFinished() const线程是否已经退出

bool isRunning() const线程是否处于运行状态

8、线程的属性

Prioritypriority() const

void setPriority( Prioritypriority)

uint stackSize() const

void setStackSize( uintstackSize)

void setTerminationEnabled( boolenabled= true )

设置是否响应terminate()函数

9、线程与事件循环

QThread中run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每一个线程都有一个属于自己的事件队列)中的事件。exec()在其内部不断做着循环遍历事件队列的工作,调用QThread的quit()或exit()方法使退出线程,尽量不要使用terminate()退出线程,terminate()退出线程过于粗暴,造成资源不能释放,甚至互斥锁还处于加锁状态。

线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI 类(如,QTimer,QTcpSocket,QProcess)。

在QApplication前创建的对象,QObject::thread()返回NULL,意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。可以用QObject::moveToThread()来改变对象及其子对象的线程亲缘关系,假如对象有父亲,不能移动这种关系。在另一个线程(而不是创建它的线程)中deleteQObject对象是不安全的。除非可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。假如没有事件循环运行,事件不会分发给对象。假如在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号,deleteLater()也不会工作。可以手工使用线程安全的函数QCoreApplication::postEvent(),在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。QCoreApplication::sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。

四、线程的同步

1、线程同步基础

临界资源:每次只允许一个线程进行访问的资源

线程间互斥:多个线程在同一时刻都需要访问临界资源

线程锁能够保证临界资源的安全性,通常,每个临界资源需要一个线程锁进行保护。

线程死锁:线程间相互等待临界资源而造成彼此无法继续执行。

产生死锁的条件:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值