本文章挑出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版本)不是抽象类。

【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. Centos-查看磁盘分区占用情况-df

    df 检查linux系统中磁盘分区占用情况 相关选项 -h 以人类友好读方式显示 -k 以KB为单位输出磁盘分区使用情况 -m   以MB为单位输出磁盘分区使用情况 -a 列出所有文件系统分区情况,包 ...

  2. 软件定义网络实验记录③--Mininet 实验——测量路径的损耗率

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

  3. Python3基础——序列类型

    开头写给自己,To Myself: 很久以来,都想要学习一门编程语言,从去年选择了python开始,反反复复重新开始了N多遍,每一次不会超过俩星期.昨天无意间翻开自己去年记的学习笔记,不禁感叹想当年我 ...

  4. vs调试程序缺少 msvcp140d.dll 解决方法

    简介一下吧: 如果只是为了解决问题请直接看第      7       点 ,谢谢. vs2013运行刚安装的opencv问题总结,尤其是电脑还很渣的情况下------花了我起码2天样子----很无奈 ...

  5. java swing 按钮事件触发两次或者多次

    按钮事件触发多次? 如果是JButton,八成是由于粗心,多次添加了监听事件 保持只添加一个监听事件就解决了~

  6. hashCode()方法源码分析

    执行代码 public class Demo06 { public static void main(String[] args) { String s="hello"; Syst ...

  7. Python中列表、元组、字典、集合与字符串,相关函数,持续更新中……

    本篇博客为博主第一次学 Python 所做的笔记(希望读者能够少点浮躁,认真阅读,平心静气学习!) 补充: 列表.元组和字符串共同属性: 属于有序序列,其中的元素有严格的先后顺序 都支持双向索引,索引 ...

  8. C语言的污垢,一个能污染内存的神秘操作!神级坑位再现~

    本文目的是为了更好的理解指针和内存管理 背景 我们定义一个变量A,修改另外一个一个变量B,导致A的值被修改,我们称它为内存污染. 案例 如下程序,正常的预期输出应该是:97 98 256 ,但正确的结 ...

  9. 【最大匹配+二分答案】HDU 2236 无题II

    题目内容 这是一个简单的游戏,在一个\(n×n\)的矩阵中,找\(n\)个数使得这\(n\)个数都在不同的行和列里并且要求这\(n\)个数中的最大值和最小值的差值最小. 输入格式 输入一个整数\(T\ ...

  10. spring注解@Transactional 和乐观锁,悲观锁并发生成有序编号问题

      需求:系统中有一个自增的合同编号,在满足并发情况下,生成的合同编号是自增的. 测试工具:Apache Jmeter 实现方法: 创建一个数据库表.编号最大值记录表 表结构类似 CREATE TAB ...