zoukankan      html  css  js  c++  java
  • 【QT】QThread源码浅析

    作者:李春港
    出处: https://www.cnblogs.com/lcgbk/p/13940142.html

    本章会挑出QThread源码中部分重点代码来说明QThread启动到结束的过程是怎么调度的。其次因为到了Qt4.4版本,Qt的多线程就有所变化,所以本章会以Qt4.0.1和Qt5.6.2版本的源码来进行浅析。

    一、QThread类的定义源码

    Qt4.0.1版本源码:

    #ifndef QT_NO_THREAD
    class Q_CORE_EXPORT QThread : public QObject
    {
    public:
        ...//省略
        explicit QThread(QObject *parent = 0);
        ~QThread();
        ...//省略
        void exit(int retcode = 0);
    
    public slots:
        void start(QThread::Priority = InheritPriority); //启动线程函数
        void terminate(); //强制退出线程函数
        void quit(); //线程退出函数
        ...//省略
    signals:
        void started(); //线程启动信号
        void finished(); //线程结束信号
        ...//省略
        
    protected:
        virtual void run() = 0;
        int exec();
        ...//省略
    };
    #else // QT_NO_THREAD
    

    Qt5.6.2版本源码:

    #ifndef QT_NO_THREAD
    class Q_CORE_EXPORT QThread : public QObject
    {
        Q_OBJECT
    public:
        ...//省略
        explicit QThread(QObject *parent = Q_NULLPTR);
        ~QThread();
        ...//省略
        void exit(int retcode = 0); //线程退出函数
        ...//省略
    public Q_SLOTS:
        void start(Priority = InheritPriority); //启动线程函数
        void terminate(); //强制退出线程函数
        void quit(); //线程退出函数
        ...//省略
    Q_SIGNALS:
        void started(QPrivateSignal); //线程启动信号
        void finished(QPrivateSignal); //线程结束信号
        
    protected:
        virtual void run();
        int exec();
        ...//省略
    };
    #else // QT_NO_THREAD
    

    从以上两个版本的代码可以看出,这些函数在声明上基本没什么差异,但是仔细看,两个版本的 run() 函数声明的是不是不一样?

    • Qt4.0.1版本run() 函数是纯虚函数,即此类为抽象类不可以创建实例,只可以创建指向该类的指针,也就是说如果你需要使用QThread来实现多线程,就必须实现QThread的派生类并且实现 run() 函数;
    • Qt5.6.2版本的run() 函数是虚函数,继承QThread类时,可以重新实现 run() 函数,也可以不实现。

    注:我查看了多个Qt版本的源码,发现出现以上差异的版本是从Qt4.4开始的。从Qt4.4版本开始,QThread类就不再是抽象类了。

    二、QThread::start()源码

    再来看看QThread::start()源码,Qt4.0.1版本和Qt5.6.2版本此部分的源码大同小异,所以以Qt5.6.2版本的源码为主,如下:

    void QThread::start(Priority priority)
    {
        Q_D(QThread);
        QMutexLocker locker(&d->mutex);
     
        if (d->isInFinish) {
            locker.unlock();
            wait();
            locker.relock();
        }
     
        if (d->running)
            return;
            
        ... ... // 此部分是d指针配置
     
    #ifndef Q_OS_WINRT
    
        ... ... // 此部分为注释
        
        d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start,
                                                this, CREATE_SUSPENDED, &(d->id));
    #else // !Q_OS_WINRT
        d->handle = (Qt::HANDLE) CreateThread(NULL, d->stackSize, (LPTHREAD_START_ROUTINE)QThreadPrivate::start,
                                                this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));
    #endif // Q_OS_WINRT
     
        if (!d->handle) {
            qErrnoWarning(errno, "QThread::start: Failed to create thread");
            d->running = false;
            d->finished = true;
            return;
        }
     
        int prio;
        d->priority = priority;
        switch (d->priority) {
        
        ... ... // 此部分为线程优先级配置
        
        case InheritPriority:
        default:
            prio = GetThreadPriority(GetCurrentThread());
            break;
        }
     
        if (!SetThreadPriority(d->handle, prio)) {
            qErrnoWarning("QThread::start: Failed to set thread priority");
        }
     
        if (ResumeThread(d->handle) == (DWORD) -1) {
            qErrnoWarning("QThread::start: Failed to resume new thread");
        }
    }
    

    挑出里面的重点来说明:

    (1)Q_D()宏定义

    在看源码的时候,当时比较好奇start函数的第一条语句 Q_D()宏定义 是什么意思,所以就看了下源码,在此也顺便讲讲,Q_D() 源码是一个宏定义,如下:

    #define Q_D(Class) Class##Private * const d = d_func()
    

    此处利用了预处理宏里的 ## 操作符:连接前后两个符号,变成一个新的符号。将Q_D(QThread)展开后,变成:QThreadPrivate * const d = d_func()。

    (2)_beginthreadex()函数
    上面d->handle = (Qt::HANDLE) _beginthreadex ( NULL, d->stackSize, QThreadPrivate::start, this, CREATE_SUSPENDED, &( d->id ) ) 语句中的函数是创建线程的函数,其原型以及各参数的说明如下:

    unsigned long _beginthreadex( 
     
    void *security,       // 安全属性,NULL为默认安全属性
     
    unsigned stack_size,  // 指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0
     
    unsigned ( __stdcall *start_address )( void * ), 
                          // 指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址)
     
    void *arglist,        // 传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针
                            //如果传入this,这个this表示调用QThread::start的对象地址,也就是QThread或者其派生类对象本身
     
    unsigned initflag,    // 线程初始状态,0:立即运行;CREATE_SUSPEND:suspended(悬挂)
     
    unsigned *thrdaddr    // 用于记录线程ID的地址
     
    );
    

    三、QThreadPrivate::start()源码

    从QThread::start()源码可以知道,QThreadPrivate::start是重点,其实际就是调用了QThreadPrivate::start(this),这个 this 表示调用QThread::start的对象地址,也就是QThread或者其派生类对象本身。因为两个Qt版本此部分的源码大同小异,所以本部分主要是以5.6.2版本的源码为主,其源码以及说明如下:

    // 参数arg就是上面所说的this
    unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
    {
        QThread *thr = reinterpret_cast<QThread *>(arg);
        QThreadData *data = QThreadData::get2(thr);
     
        // 创建线程局部存储变量,存放线程id
        qt_create_tls();
        TlsSetValue(qt_current_thread_data_tls_index, data);
        data->threadId = reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId()));
     
        QThread::setTerminationEnabled(false);
     
        {
            QMutexLocker locker(&thr->d_func()->mutex);
            data->quitNow = thr->d_func()->exited;
        }
     
        if (data->eventDispatcher.load()) // custom event dispatcher set?
            data->eventDispatcher.load()->startingUp();
        else
            createEventDispatcher(data);
            
        ...//省略
        
        emit thr->started(QThread::QPrivateSignal()); // 发射线程启动信号
        QThread::setTerminationEnabled(true);
        thr->run(); // 调用QThread::run()函数 -- 线程函数
     
        finish(arg); //结束线程
        return 0;
    }
    

    由上述源码可以看出,实际上 run() 函数是在这里调用的,并且发出了 started() 启动信号,等到 run() 函数执行完毕,最后是调用了 QThreadPrivate::finish 函数结束线程,并且在finish内会发出 QThread::finished() 线程已结束的信号。

    四、QThread::run()源码

    再看看QThread::run()函数的源码。在上面 《2.1 QThread类的定义源码》的小节,我们可以看到两个Qt版本声明此方法的方式不一样,Qt-4.0版本将此定义为了纯虚函数,而Qt-5.6版本将此定义为了虚函数,那我们就看看Qt-5.6版本中,QThread::run()是如何定义的,如下:

    void QThread::run()
    {
        (void) exec();
    }
    
    1. 每一个 Qt 应用程序至少有一个 事件循环 ,就是调用了 QCoreApplication::exec() 的那个事件循环。不过,QThread也可以开启事件循环。只不过这是一个受限于线程内部的事件循环。因此我们将处于调用main()函数的那个线程,并且由 QCoreApplication::exec() 创建开启的那个事件循环成为 主事件循环 ,或者直接叫 主循环 。注意,QCoreApplication::exec()只能在调用main()函数的线程调用。主循环所在的线程就是主线程,也被成为 GUI 线程,因为所有有关 GUI 的操作都必须在这个线程进行。QThread的局部事件循环则可以通过在 QThread::run() 中调用 QThread::exec() 开启。

    2. 我们通过以上源码可以看到,它的定义很简单,就是调用了一个函数:QThread::exec() 开启线程中的 事件循环 ,我们也可以通过继承QThread,重写run()函数的方式,让其实现相对复杂的逻辑代码。如果你的线程需要将某些槽函数在本线程完成的话,就必须开启事件循环,否则在线程内无法响应各种信号并作出相应的行为。

    小结: 比Qt-4.4版本更早的版本中,我们使用QThread启动线程时,就必须要实现继承于QThread的派生类,并且一定要重写run函数,若需要使用事件循环,就需要在run函数中添加exec()。到了Qt4.4版本之后(包括Qt4.4版本),QThread就不是抽象类了,不派生也可以实例化,在不重写QThread::run()方法,start启动线程是默认启动事件循环的。

    注:当程序跑到了exec()代码时,位于exec()后面的代码就不会再被执行,除非我们使用quit、exit等退出语句来退出事件循环,退出后,程序才会继续执行位于exec()后面的代码。

    五、QThread::quit()、QThread::exit()、QThread::terminate()源码

    线程停止函数的区别,从Qt源码来分析:

    (1)QThread::quit()、QThread::exit()

    //QThread::quit()声明
    void quit();
    //QThread::quit()定义
    void QThread::quit()
    { exit(); }
    
    //QThread::exit()声明
    void exit(int retcode = 0);
    //QThread::exit()定义
    void QThread::exit(int returnCode)
    {
        Q_D(QThread);
        QMutexLocker locker(&d->mutex);
        d->exited = true;
        d->returnCode = returnCode;
        d->data->quitNow = true;
        for (int i = 0; i < d->data->eventLoops.size(); ++i) {
            QEventLoop *eventLoop = d->data->eventLoops.at(i);
            eventLoop->exit(returnCode);
        }
    }
    

    由以上源码可知,QThread::quit()QThread::exit(0) 的调用是等效的,都是告诉线程的事件循环,以返回码0(成功)退出。如果线程没有事件,则此函数不执行任何操作,也就是无效的。当线程拥有事件循环并且正处于 事件循环(QThread::exec()) 的状态时,调用 QThread::quit()或者QThread::exit() 线程就会马上停止,否则不会立刻停止线程,直到线程处于事件循环也就是正在执行 QThread::exec() 时,才会停止线程。

    如果重复调用 QThread::quit()或者QThread::exit() 会有什么影响吗?
    重复调用 QThread::quit()或者QThread::exit() 也不会有什么影响,因为只有拥有事件循环的线程,这两个函数才会生效停止线程的功能。

    (2)QThread::terminate()

    void QThread::terminate()
    {
        Q_D(QThread);
        QMutexLocker locker(&d->mutex);
        if (!d->running)
            return;
        if (!d->terminationEnabled) {
            d->terminatePending = true;
            return;
        }
    
    // Calling ExitThread() in setTerminationEnabled is all we can do on WinRT
    #ifndef Q_OS_WINRT
        TerminateThread(d->handle, 0);
    #endif
        QThreadPrivate::finish(this, false); //结束线程函数
    }
    

    在这个函数定义的最后一个语句,是调用了 QThreadPrivate::finish(this, false); 函数,其函数作用是直接退出线程,无论线程是否开启了事件循环都会生效,会马上终止一个线程,但这个函数存在非常不安定因素,不推荐使用

    如果重复调用 QThread::terminate() 会有什么影响吗?
    没有影响。我们可以看到函数体里面的第三条语句,它首先会判断线程是否还在运行中,如果不是,会直接退出函数,就不会继续往下执行调用QThreadPrivate::finish(this, false); 函数了。

    六、章节小结

    相信看了以上的一些QThread源码,都大概知道了QThread类的本质以及QThread开启到结束的过程。这里我再简单总结下:

    (1)QThread的本质:

    • QThread 是用来管理线程的,它所依附的线程和它管理的线程并不是同一个东西;
    • QThread 所依附的线程,就是执行 QThread t 或 QThread * t=new QThread 所在的线程;
    • QThread 管理的线程,就是 run 启动的线程,也就是次线程。

    (2)在这里针对Qt4.4版本之后(包括Qt4.4版本)简单汇总一下线程启动到结束的过程:

    • QThread对象或者QThread派生类对象显式调用QThread类中的外部start()方法;
    • QThread::start()方法再调用QThreadPrivate::start()方法;
    • 在QThreadPrivate::start()方法内调用了QThread::run()虚函数,对使用者来说到了这里才是真正进入了一个新的线程里面。也就是说定义QThread对象或者QThread派生类对象的时候,还是在原来的线程里面,只有进入run函数才是进入了新的线程;
    • 在QThreadPrivate::start()方法调用QThread::run()虚函数结束后,就会继续调用QThreadPrivate::finish()函数来结束线程,并发出线程结束的信号finished()。

    (3)QThread::quit()、QThread::exit()、QThread::terminate():

    • 对线程重复使用这三个停止线程的函数,没有任何影响;
    • 尽量不要使用QThread::terminate()停止线程,此方式是强制退出线程,没有安全保障。
    • 调用QThread::quit()和QThread::exit()一样。

    (4)Qt各版本QThread类的变化:

    • Qt4.4版本之前QThread类是属于抽象类, Qt4.4版本之后(包括4.4版本)不是抽象类。
  • 相关阅读:
    win10 UWP button
    内网分享资源
    内网分享资源
    CF724F Uniformly Branched Trees
    win10 UWP FlipView
    win10 UWP FlipView
    win10 UWP FlipView
    搭建阿里云 centos mysql tomcat jdk
    搭建阿里云 centos mysql tomcat jdk
    win10 UWP 申请微软开发者
  • 原文地址:https://www.cnblogs.com/lcgbk/p/13940142.html
Copyright © 2011-2022 走看看