Qt线程使用总结:QtConcurrent、QThreadPool、QRunnable、QThread详解

写这几片博文,是自己对Qt线程使用的一些总结。会尽量写的详细,如果只是学习Qt线程的基本使用可以,参考转载的那篇《Qt线程基础》。

Qt线程的几种使用形式:

  1. QThread
  2. QThreadPool+QRunnable
  3. QtConcurrent

一些常用替换线程的方案:

           1)如果需要对每个发来的信号都做出处理,那么有两种方式来解决,即在信号与槽的connect函数中明确第五个参数,将其设置   成DirectConnection方式阻塞时编程,或者设置成BlockingQueuedConnection按照加锁队列都可以很好的解决; 
         2)如果只需要对最新的信号做处理,那么这里也给出两种方案来处理: 
             a、槽所在线程设置bool状态,信号所在线程通过判定这个bool的状态来确定是否发送信号; 
            b、槽执行完毕,则向信号所在线程发送返回值,信号所在线程通过判定发来的这个返回值来判定是否继续对槽所在线程发送新的信号。

            这里字面的意思,槽函数是类似中断形式执行。Qt5.7版本测试结果其实并不是这样的,在单线程直接连接的形式下,信号需要等到槽函数执行完毕,才触发新的信号,并且信号在槽函数执行期间应该是没有排队的。后续还需要测试,排队、多线程的信号与槽的工作模式。 

  1. QEvent::ProcessEvent(AllEvent)这个可以很好的防治界面卡死的情况,或者应用一些非阻赛等待情况。第一次接触是写一个FTP客户端,FTP协议经常需要和服务器来回确认,当发送一个请求,等待服务器确认回复,需要阻赛等待在那,一般是一个while配合一个等待时长,while里面不用这个的话,界面就会卡死。当然也可以设计一个好的定时扫描结构,但是这样程序结构,就显得不那么直接,清晰。
  2. 间隔为0 的QTimer来做定时任务。这里说一下关于信号出发时间间隔(50ms)小于槽函数执行时间(100ms):
  3. QSocketNotifier 、QNetworkAccessManager 、QIODevice::readyRead()这几个主要是针对网络延时也会经常用到,但应用场景基本都是网络方面。可以省去你一直去轮询某些IO口。

   说了这么多,都是为了了解Qt多线程周边知识,恰当的场景选择恰当的技术。

QThread

   使用最多的应该是QThread,QThread的使用场景,双向交互,线程需要上报工作情况,外部控制线程内部工作情况,其使用方法:

  1. 直接使用QThread,调用moveToThread函数。
  2. 继承QThread,重写run函数。

首先我们看一下QThread的使用常识:

  • 在主线程中创建QThread或其子类,该对象存在于主线程中。
  • QThread的大多数成员方法是线程的控制接口,并设计成从旧线程中被调用。不要使用moveToThread()将该接口移动到新创建的线程中;调用moveToThread(this)被视为不好的实践。
  • moveToThread的对象,必须是QObject的子类,且没有parent。
  • moveToThread只能将该对象的槽函数放置次线程中运行,其他成员函数则不行。
  • QThread子类中定义的其他成员可在两个线程中访问。开发人员负责访问的控制。一个典型的策略是在start()被调用前设置成员变量。一旦工作线程开始运行,主线程不应该操作其他成员。当工作线程终止后,主线程可以再次访问其他成员。这是一个在线程开始前传递参数并在结束后收集结果的便捷的策略。同理成员函数。
  • QThread子类,重写的run函数中可访问全局变量,但依然要注意数据安全。

当然我们使用最多的是QThread于connect的关系,所以我们总结一下QThread于信号槽的使用规律:

首先我们要记住一些分析的铁律,紧随这些原则,将清洗的看清楚信号与槽在多线程中的工作,

  • 直接连接(Direct Connection)
    • 当信号发射时,槽函数将直接被调用。 
    • 无论槽函数所属对象在哪个线程,槽函数都在发射者所在线程执行。 
  • 队列连接(Queued Connection)
    • 当控制权回到接受者所在线程的事件循环式,槽函数被调用。 
    • 槽函数在接收者所在线程执行。
  1. 主线程对象发出信号连接QThread子类的槽函数,QThread子类对象在主线程创建的,无论采用哪种连接方式,槽函数都属在主线程调用。(如果在重写的run函数中调用了槽函数,此时槽函数在次线程执行,注意数据安全)。

如果需要槽函数在次线程执行: 

class Thread:public QThread 
{ 
    Q_OBJECT 
public: 
    Thread(QObject* parent=0):QThread(parent) 
    { 
        moveToThread(this); 
    } 
public slots: 
    void slot_main() 
    { 
        qDebug()<<"from thread slot_main:" <<currentThreadId(); 
    } 

这就是上面提到的不好的用法,线程对象把自己移到次线程,这样他的槽函数就在次线程中行。 这可以工作,但这是 Bradley T. Hughes 强烈批判的用法。 这种情况connect是在主线程编写,主线程发出信号。

2.次线程run中发出信号,槽函数可以是发出信号对象自身的槽函数,自发自收,都是次线程中行。槽函数是QThread子类的槽函数,或者主线程中对象的槽函数,这里的种情况需要你指明run中connect中的连接方式,直连则该槽函数在次线程中执行(可能发生数据安全问题),列队则在主线程执行。

总结就是:分析发出信号的对象和接受信号对象所在的线程,再通过连接方式,判断槽函数在哪里执行。(小白在使用中就有在run中创建对象-因为多非槽函数都需要在次线程中执行,通过指针引出来,再connect与其他模块交互,指明连接方式为列队形式,所以相关执行都在次线程中执行)。这里记住moveToThread只能将槽函数移到次线程中,也就很容易理解QThread的推荐用法:

int main(int argc, char *argv[]) 
{ 
    QCoreApplication a(argc, argv); 
    qDebug()<<"main thread:"<<QThread::currentThreadId(); 
    QThread thread; 
    Object obj; 
    Dummy dummy; 
    obj.moveToThread(&thread); 
    QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot())); 
    thread.start(); 
    dummy.emitsig(); 
    return a.exec(); 
}

Qt跨线程的信号与槽,最终也是通过事件完成的,所以在使用QThread的时候,跨线程信号与槽run中必须开启时间循环。

QThreadPool+QRunnable

QThreadPool是QObject的子类,QRunnable不是且是一个虚基类。QRunnable的子类对象+线程池(自定义或者全局线程池)固定搭配。

这里只说两点:

1、QRunnable 的autoDelete默认是true,及QRunnable 的子类run函数执行完,线程池会自动帮你删除该对象。

2、QRunnable 如何与外界进行通信

       方法1:QRunnable并不继承自QObject类,因此无法使用信号/槽的方式与外界进行通信。我们就必须的使用其他方法,这里给大家介绍的是使用:QMetaObject::invokeMethod()函数、自定义事件也可以。

       方法2:使用多重继承的方法,任务子类同时继承自QRunnable和QObject。

//书写形式--不使用信号与槽
void Runnable::run(){
    QMetaObject::invokeMethod(m_receiver , "functionname" , Qt::QueuedConnection , Q_ARG(bool , enable));
   //m_reveicer 为Runnable的成员变量
}


//继承QObject,使用信号与槽
void Runnable::run(){
    connect(signalObj , SIGNAL(sig()) ,m_reveicer ,SLOT(slot()) , Qt::QueuedConnection  )
   //m_reveicer 为Runnable的成员变量 signalObj 运行在次线程的对象,还可以把这个对象的指针引到外部使用,连接它的槽函数,但连接方式一定要注意
}

QtConcurrent

该命名空间的函数都属为多线程服务的 

这里对于几种不同的函数都有讲到:普通函数、常量成员函数、非常量成员函数。

该run函数介绍里面没有涉及事件循环,所以次线程发出信号可以(事件和inovkMeth也可以),但是跨线程槽,在次线程内应该是接受不到信号执行槽函数的。 (该部分主要是针对,单独函数在次线程执行,所以不涉及到信号触发次线程中执行槽函数,主要是函数执行进度,及外部对线程的控制)

  1. QtConcurrent::run(Function function,...) , 这里贴一个地址:QtConcurrent::run的几种用法
  2. QtConcurrent::map、mapped、mappedRedduced、filter(小白都木有用过,仅作学习使用,后续有系统的学习补充)

运用QtConcurrent中的MapReduce模型进行简单的C++并行运算

Qt 之 Concurrent Map 和 Map-Reduce

Qt 之 Concurrent 框架

最后关于多线程相关的QFuture和QFutureWatcher:QFuture和QFutureWatcher

无论在分布式还是多线程中的异步调用都涉及到这个,Fututre这种机制Qt也是源于Java,在高性能分布式框架中,当产生异步调用时,跨机器调用时,这种高效等待调用结果方式,可以大大提高程序效率。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值