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

一、QThread类的定义源码

Qt4.0.1版本源码:

  1. #ifndef QT_NO_THREAD
  2. class Q_CORE_EXPORT QThread : public QObject
  3. {
  4. public:
  5. ...//省略
  6. explicit QThread(QObject *parent = 0);
  7. ~QThread();
  8. ...//省略
  9. void exit(int retcode = 0);
  10. public slots:
  11. void start(QThread::Priority = InheritPriority); //启动线程函数
  12. void terminate(); //强制退出线程函数
  13. void quit(); //线程退出函数
  14. ...//省略
  15. signals:
  16. void started(); //线程启动信号
  17. void finished(); //线程结束信号
  18. ...//省略
  19. protected:
  20. virtual void run() = 0;
  21. int exec();
  22. ...//省略
  23. };
  24. #else // QT_NO_THREAD

Qt5.6.2版本源码:

  1. #ifndef QT_NO_THREAD
  2. class Q_CORE_EXPORT QThread : public QObject
  3. {
  4. Q_OBJECT
  5. public:
  6. ...//省略
  7. explicit QThread(QObject *parent = Q_NULLPTR);
  8. ~QThread();
  9. ...//省略
  10. void exit(int retcode = 0); //线程退出函数
  11. ...//省略
  12. public Q_SLOTS:
  13. void start(Priority = InheritPriority); //启动线程函数
  14. void terminate(); //强制退出线程函数
  15. void quit(); //线程退出函数
  16. ...//省略
  17. Q_SIGNALS:
  18. void started(QPrivateSignal); //线程启动信号
  19. void finished(QPrivateSignal); //线程结束信号
  20. protected:
  21. virtual void run();
  22. int exec();
  23. ...//省略
  24. };
  25. #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版本的源码为主,如下:

  1. void QThread::start(Priority priority)
  2. {
  3. Q_D(QThread);
  4. QMutexLocker locker(&d->mutex);
  5. if (d->isInFinish) {
  6. locker.unlock();
  7. wait();
  8. locker.relock();
  9. }
  10. if (d->running)
  11. return;
  12. ... ... // 此部分是d指针配置
  13. #ifndef Q_OS_WINRT
  14. ... ... // 此部分为注释
  15. d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start,
  16. this, CREATE_SUSPENDED, &(d->id));
  17. #else // !Q_OS_WINRT
  18. d->handle = (Qt::HANDLE) CreateThread(NULL, d->stackSize, (LPTHREAD_START_ROUTINE)QThreadPrivate::start,
  19. this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));
  20. #endif // Q_OS_WINRT
  21. if (!d->handle) {
  22. qErrnoWarning(errno, "QThread::start: Failed to create thread");
  23. d->running = false;
  24. d->finished = true;
  25. return;
  26. }
  27. int prio;
  28. d->priority = priority;
  29. switch (d->priority) {
  30. ... ... // 此部分为线程优先级配置
  31. case InheritPriority:
  32. default:
  33. prio = GetThreadPriority(GetCurrentThread());
  34. break;
  35. }
  36. if (!SetThreadPriority(d->handle, prio)) {
  37. qErrnoWarning("QThread::start: Failed to set thread priority");
  38. }
  39. if (ResumeThread(d->handle) == (DWORD) -1) {
  40. qErrnoWarning("QThread::start: Failed to resume new thread");
  41. }
  42. }

挑出里面的重点来说明:

(1)Q_D()宏定义

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

  1. #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 ) ) 语句中的函数是创建线程的函数,其原型以及各参数的说明如下:

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

三、QThreadPrivate::start()源码

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

  1. // 参数arg就是上面所说的this
  2. unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
  3. {
  4. QThread *thr = reinterpret_cast<QThread *>(arg);
  5. QThreadData *data = QThreadData::get2(thr);
  6. // 创建线程局部存储变量,存放线程id
  7. qt_create_tls();
  8. TlsSetValue(qt_current_thread_data_tls_index, data);
  9. data->threadId = reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId()));
  10. QThread::setTerminationEnabled(false);
  11. {
  12. QMutexLocker locker(&thr->d_func()->mutex);
  13. data->quitNow = thr->d_func()->exited;
  14. }
  15. if (data->eventDispatcher.load()) // custom event dispatcher set?
  16. data->eventDispatcher.load()->startingUp();
  17. else
  18. createEventDispatcher(data);
  19. ...//省略
  20. emit thr->started(QThread::QPrivateSignal()); // 发射线程启动信号
  21. QThread::setTerminationEnabled(true);
  22. thr->run(); // 调用QThread::run()函数 -- 线程函数
  23. finish(arg); //结束线程
  24. return 0;
  25. }

由上述源码可以看出,实际上 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()是如何定义的,如下:

  1. void QThread::run()
  2. {
  3. (void) exec();
  4. }
  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()

  1. //QThread::quit()声明
  2. void quit();
  3. //QThread::quit()定义
  4. void QThread::quit()
  5. { exit(); }
  6. //QThread::exit()声明
  7. void exit(int retcode = 0);
  8. //QThread::exit()定义
  9. void QThread::exit(int returnCode)
  10. {
  11. Q_D(QThread);
  12. QMutexLocker locker(&d->mutex);
  13. d->exited = true;
  14. d->returnCode = returnCode;
  15. d->data->quitNow = true;
  16. for (int i = 0; i < d->data->eventLoops.size(); ++i) {
  17. QEventLoop *eventLoop = d->data->eventLoops.at(i);
  18. eventLoop->exit(returnCode);
  19. }
  20. }

由以上源码可知,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()

  1. void QThread::terminate()
  2. {
  3. Q_D(QThread);
  4. QMutexLocker locker(&d->mutex);
  5. if (!d->running)
  6. return;
  7. if (!d->terminationEnabled) {
  8. d->terminatePending = true;
  9. return;
  10. }
  11. // Calling ExitThread() in setTerminationEnabled is all we can do on WinRT
  12. #ifndef Q_OS_WINRT
  13. TerminateThread(d->handle, 0);
  14. #endif
  15. QThreadPrivate::finish(this, false); //结束线程函数
  16. }

在这个函数定义的最后一个语句,是调用了 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版本)不是抽象类。

【QT】QThread源码浅析的更多相关文章

  1. 【QT】 QThread部分源码浅析

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

  2. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  4. Struts2源码浅析-ConfigurationProvider

    ConfigurationProvider接口 主要完成struts配置文件 加载 注册过程 ConfigurationProvider接口定义 public interface Configurat ...

  5. (转)【深入浅出jQuery】源码浅析2--奇技淫巧

    [深入浅出jQuery]源码浅析2--奇技淫巧 http://www.cnblogs.com/coco1s/p/5303041.html

  6. HashSet其实就那么一回事儿之源码浅析

    上篇文章<HashMap其实就那么一回事儿之源码浅析>介绍了hashMap,  本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap ...

  7. Android 手势识别类 ( 三 ) GestureDetector 源码浅析

    前言:上 篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势.所 以,用户绘制出的一个完整的手势是需要一定的代码 ...

  8. Android开发之Theme、Style探索及源码浅析

    1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...

  9. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

随机推荐

  1. Dell XPS 7590 Hackintosh

    网上主流引导Hackintosh的工具有Chameleon, Clover和OpenCore. 但是随着Hackintosh重要驱动开发团队acidanthera逐渐转向OpenCore,后者显然才是 ...

  2. Matlab中image、imagesc和imshow函数用法解析

    来源:https://blog.csdn.net/zhuiyuanzhongjia/article/details/79621813 1.显示RGB图像 相同点:这三个函数都是把m*n*3的矩阵中的数 ...

  3. VMware ESXi 客户端连接控制台时,提示“VMRC 控制台连接已断开...正在尝试重新连接”的解决方法

    故障描述: 通过 VMware vSphere Client 连接到安装 VMware ESXi 虚拟环境的主机时,当启动其中的虚拟机后,无法连接到控制台. 选择"控制台"时,控制 ...

  4. CDH5部署三部曲之二:部署和设置

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. .NET 5.0 RC2 发布,正式版即将在 11 月 .NET Conf 大会上发布

    原文:http://dwz.win/ThX 作者:Richard 翻译:精致码农-王亮 说明:本译文舍弃了少许我实在不知道如何翻译但又不是很重要的语句. 今天(10月13日)我们发布了 .NET 5. ...

  6. 爬虫之Selenium

    简介 selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如 ...

  7. day31 Pyhton 面向对象的基础 三大特性

    一.内容回顾 封装 1.概念 笔记 2.__名字 在类的外部就不能用了 3.私有化的 不能被子类继承,也不能在其他任何类中调用 三个装饰器方法(装饰类中的方法) 1.不被修饰的  普通方法,会使用对象 ...

  8. faker使用

    laravel中faker的方法总结   展开 laravel faker用法总结 安装 composer require fzaninotto/faker   一.基础方法: 随机数:randomD ...

  9. TCP/IP的十个问题

    一.TCP/IP模型 TCP/IP协议模型(Transmission Control Protocol/Internet Protocol),包含了一系列构成互联网基础的网络协议,是Internet的 ...

  10. Anderson《空气动力学基础》5th读书笔记导航

    没错,在2018年,我正式启程了安德森教授这本空气动力学圣经的阅读,为了深入理解概念,特写此刊,边读边写,2020年一定写完,写不完我就/¥@%¥---! 以下是导航: 第一章任务图: 第一章思维导图 ...