本文章挑出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. _1_html_框架

    创:18_3_2017修:20_3_2017 什么是html? --超文本标记语言 --告诉浏览器内容的语义 html页面的框架是什么? <!DOCTYPE html> <!--DT ...

  2. Python练习题 047:Project Euler 020:阶乘结果各数字之和

    本题来自 Project Euler 第20题:https://projecteuler.net/problem=20 ''' Project Euler: Problem 20: Factorial ...

  3. 实验 3:Mininet 实验——测量路径的损耗率

    一.实验目的 在实验 2 的基础上进一步熟悉 Mininet 自定义拓扑脚本,以及与损耗率相关的设定;初步了解 Mininet 安装时自带的 POX 控制器脚本编写,测试路径损耗率. 二.实验任务 h ...

  4. Visual C++中各种文件的作用(详细)

    参考:http://blog.sina.com.cn/s/blog_6975d67c0100r3kx.html DSW:全称是Developer Studio Workspace,最高级别的配置文件, ...

  5. P3545 [POI2012]HUR-Warehouse Store

    题目描述 n天.第i天上午会进货Ai件商品,中午的时候会有顾客需要购买Bi件商品,可以选择满足顾客的要求,或是无视掉他. 如果要满足顾客的需求,就必须要有足够的库存.问最多能够满足多少个顾客的需求. ...

  6. ubuntu 19.10 中防火墙iptables配置

    $sudo which iptables   /usr/sbin/iptables说明有安装 如果没有安装,那么使用sudo apt-get install iptables 安装. 刚装机,是这个样 ...

  7. Flask实现websocket

    from flask import Flask,request user_socket_dict = {} app = Flask(__name__) @app.route("/conn_w ...

  8. Redis不重启的情况下 切换持久化模式

    确保redis版本在2.2以上 [root@localhost /]# redis-server -v Redis server v=4.0.10 sha=00000000:0 malloc=jema ...

  9. 经验分享:计算机 web 浏览器——访问剪切板图片

      有时候,我们希望能访问用户的剪切板,来实现一些方便用户的功能:但是另一方面,剪切板里的数据对用户来说又是非常隐私的,所以浏览器在获取信息方面有安全限制,同时也提供访问接口. 当我们需要实现在富文本 ...

  10. lvs搭建dr负载均衡集群

    一,查看本地centos的版本: [root@localhost lib]# cat /etc/redhat-release CentOS Linux release 8.1.1911 (Core) ...