深入探索Qt框架系列之信号槽原理(三)
前面两篇分别介绍了QObject::connect
和QMetaObject::Connection
,那么信号槽机制的基础已经介绍完了,本文将介绍信号槽机制是如何从信号到槽的,以及多线程下是如何工作的。
信号槽机制源码解析
1. 信号的触发
以该系列的第一篇文章中的示例举例:
test_moc.h:
class test_moc : public QObject {Q_OBJECT
public:test_moc(QObject* parent = nullptr): QObject(parent){}QString name { "123" };using INTTYPE = int;
public slots:void on_TestSlot() { qDebug() << __FUNCTION__; }void on_TestSlot_Param(int num) { qDebug() << __FUNCTION__ << num; }void on_TestSlot_Param(QString num) { qDebug() << __FUNCTION__ << num; }signals:void sigTestSignals();void sigTestSignals_Param(INTTYPE num);
};
main.cpp:
int main(int argc, char* argv[])
{QApplication a(argc, argv);test_moc m1, m2;QObject::connect(&m2, &test_moc::sigTestSignals_Param, &m1, [=](int num) {qDebug() << __FUNCTION__ << num;});emit m2.sigTestSignals_Param(1);return a.exec();
}
信号触发是通过emit
宏实现的,在《深入探索Qt框架系列之元对象编译器》一文中已经介绍了,emit
是一个空宏,并且在经过元对象编译器处理生成的代码中包含了信号的实现:
// SIGNAL 0
void test_moc::sigTestSignals()
{QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}// SIGNAL 1
void test_moc::sigTestSignals_Param(int _t1)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
所以信号的触发就是调用了QMetaObject::activate
函数。
2. 信号函数底层实现原理
QMetaObject::activate
函数源码就是调用了QMetaObject::doActivate
,下面我们通过源码来解析:
不想看源码的可以直接看下面的小结内容。
template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{QObjectPrivate *sp = QObjectPrivate::get(sender);// ... ... 省略非关键代码bool senderDeleted = false;{Q_ASSERT(sp->connections.loadAcquire());QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();// 获取信号索引对应ConnectionListconst QObjectPrivate::ConnectionList *list;if (signal_index < signalVector->count())list = &signalVector->at(signal_index);elselist = &signalVector->at(-1);// 这是发送信号的线程ID(也就是调用emit所在线程的ID)Qt::HANDLE currentThreadId = QThread::currentThreadId();// 判断发送信号的线程ID和发送者对象所在线程ID是否一致// 两者是有可能不一致的,比如将发送者对象通过moveToThread()方法移动到另外的线程bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData.loadRelaxed()->threadId.loadRelaxed();// We need to check against the highest connection id to ensure that signals added// during the signal emission are not emitted in this emission.// 为了确保在信号发送期间新增的信号不会在当前的发送过程中被发送,我们需要检查最高的连接IDuint highestConnectionId = connections->currentConnectionId.loadRelaxed();do {QObjectPrivate::Connection *c = list->first.loadRelaxed();if (!c)continue;do {QObject * const receiver = c->receiver.loadRelaxed();if (!receiver)continue;QThreadData *td = c->receiverThreadData.loadRelaxed();if (!td)continue;// 判断是否跨线程bool receiverInSameThread;if (inSenderThread) {receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();} else {// need to lock before reading the threadId, because moveToThread() could interfereQMutexLocker lock(signalSlotLock(receiver));receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();}// determine if this connection should be sent immediately or// put into the event queue// 判断此连接是直接调用还是放入事件队列if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)) {// 通过postEvent实现跨线程通信queued_activate(sender, signal_index, c, argv);continue;
#if QT_CONFIG(thread)} else if (c->connectionType == Qt::BlockingQueuedConnection) {if (receiverInSameThread) {// 在同一个线程下不能使用BlockingQueuedConnection连接qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: ""Sender is %s(%p), receiver is %s(%p)",sender->metaObject()->className(), sender,receiver->metaObject()->className(), receiver);}// 定义局部信号量,用来同步发送者和接收者QSemaphore semaphore;{QBasicMutexLocker locker(signalSlotLock(sender));if (!c->receiver.loadAcquire())continue;QMetaCallEvent *ev = c->isSlotObject ?new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,sender, signal_index, argv, &semaphore);// 发送到接收者线程的事件队列QCoreApplication::postEvent(receiver, ev);}// 发送线程阻塞semaphore.acquire();continue;
#endif}// ... ... 省略代码的功能:// 根据槽类型做不同的处理:槽对象调用、函数指针调用、元调用(metacall)...} while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);// 上面是内层循环,处理当前信号索引下的所有连接对象} while (list != &signalVector->at(-1) &&//start over for all signals;((list = &signalVector->at(-1)), true));// 上面是外层循环,强行再做一次循环,目的是检查所有连接对象。if (connections->currentConnectionId.loadRelaxed() == 0)senderDeleted = true;}// ... ...
}
2.1. 小结
函数内部处理的整个流程如下:
多线程下的信号槽
1. 如何判断信号槽是否跨线程?
在正常情况下,我们一般使用AutoConnection
进行连接,在明确跨线程时会用QueuedConnection
或BlockingQueuedConnection
。这里我们只讨论在用AutoConnection
时,Qt底层是如何判断的。
从上面一节的源码可以看到,在触发信号函数时,底层涉及到三个线程,分别是:
- 发送信号线程
- 发送者所在线程
- 接收者所在线程
这三个线程是要区分开来的,底层实现的逻辑是发送信号线程和接收者所在线程进行比较,如果不一致,则代表跨线程。
发送信号线程不一定是发送者所在线程,发送者所在线程可以通过moveToThread()转移。
接收者所在线程的数据存储在哪里?
在《深入探索Qt框架系列之信号槽原理(二)》一文中介绍了QMetaObject::Connection
结构中有维护接收者对象所在线程的数据。
2. 跨线程通信是如何实现的?
还是通过上面的源码可以知道,跨线程通信就是通过QCoreApplication::postEvent()
函数实现的。
该函数将事件数据对象(QEvent
的派生实例)和接收事件的QObject
发送到接收事件的QObject
所在的线程事件队列中,事件队列依次处理,处理到对应事件时就是调用槽函数的时候。
本文不再展开介绍postEvent()
是如何工作的了,因为这里涉及到线程的事件队列机制,后面我们再详细介绍。
相关文章:

深入探索Qt框架系列之信号槽原理(三)
前面两篇分别介绍了QObject::connect和QMetaObject::Connection,那么信号槽机制的基础已经介绍完了,本文将介绍信号槽机制是如何从信号到槽的,以及多线程下是如何工作的。 信号槽机制源码解析 1. 信号的触发 以该系列的第一篇文章中的示例举…...

npm镜像源管理、nvm安装多版本node异常处理
查看当前使用的镜像源 npm config get registry --locationglobal 设置使用官方源 npm config set registry https://registry.npmjs.org/ --locationglobal 设置淘宝镜像源 npm config set registry https://registry.npm.taobao.org/ --locationglobal 需要更改淘宝镜像源地址…...

异步编程的魔力:如何显著提升系统性能
异步编程的魔力:如何显著提升系统性能 今天我们来聊聊一个对开发者非常重要的话题——异步编程。异步编程是提升系统性能的一种强大手段,尤其在需要高吞吐量和低时延的场景中,异步设计能够显著减少线程等待时间,从而提升整体性能。 异步设计如何提升系统性能? 我们通过…...

优选算法一:双指针算法与练习(移动0)
目录 双指针算法讲解 移动零 双指针算法讲解 常见的双指针有两种形式,一种是对撞指针,一种是快慢指针。 对撞指针:一般用于顺序结构中,也称左右指针。 对撞指针从两端向中间移动。一个指针从最左端开始,另一个从最…...

数据结构第二篇【关于java线性表(顺序表)的基本操作】
【关于java线性表(顺序表)的基本操作】 线性表是什么?🐵🐒🦍顺序表的定义🦧🐶🐵创建顺序表新增元素,默认在数组最后新增在 pos 位置新增元素判定是否包含某个元素查找某个…...
人工智能和大模型的区别
人工智能(AI)和大模型是两个相关但有区别的概念。理解它们之间的区别有助于更好地掌握现代科技的发展动态。 人工智能(AI) 人工智能(Artificial Intelligence, AI)是一个广义的概念,指的是通过…...
k8s处于pending状态的原因有哪些
k8s处于pending状态的原因 资源不足:集群中的资源(如CPU、内存)不足以满足Pod所需的资源请求,导致Pod无法调度。 调度器问题:调度器无法为Pod找到合适的节点进行调度,可能是由于节点资源不足或调度策略配置…...

【C++】入门(一):命名空间、缺省参数、函数重载
目录 一、关键字 二、命名空间 问题引入(问题代码): 域的问题 1.::域作用限定符 的 用法: 2.域的分类 3.编译器的搜索原则 命名空间的定义 命名空间的使用 举个🌰栗子: 1.作用域限定符指定命名空间名称 2. using 引入…...

深入分析 Android Activity (四)
文章目录 深入分析 Android Activity (四)1. Activity 的生命周期详解1.1 onCreate1.2 onStart1.3 onResume1.4 onPause1.5 onStop1.6 onDestroy1.7 onRestart 2. Activity 状态的保存与恢复2.1 保存状态2.2 恢复状态 3. Activity 的启动优化3.1 延迟初始化3.2 使用 ViewStub3.…...

Java实现顺序表
Java顺序表 前言一、线性表介绍常见线性表总结图解 二、顺序表概念顺序表的分类顺序表的实现throw具体代码 三、顺序表会出现的问题 前言 推荐一个网站给想要了解或者学习人工智能知识的读者,这个网站里内容讲解通俗易懂且风趣幽默,对我帮助很大。我想与…...
刷题笔记1:如何科学的限制数字溢出问题
LCR 192. 把字符串转换成整数 (atoi) - 力扣(LeetCode) 我们以力扣的此题目为例,简述在诸如大数运算等问题中如何限制数字溢出问题。 先来直接看看自己的处理方式: class Solution { public:int myAtoi(string str) {int pcur0;…...

社区供稿丨GPT-4o 对实时互动与 RTC 的影响
以下文章来源于共识粉碎机 ,作者AI芋圆子 前面的话: GPT-4o 发布当周,我们的社区伙伴「共识粉碎机」就主办了一场主题为「GPT-4o 对实时互动与 RTC 的影响」讨论会。涉及的话题包括: GPT-4o 如何降低延迟(VAD 模块可…...

基于Linux的文件操作(socket操作)
基于Linux的文件操作(socket操作) 1. 文件描述符基本概念文件描述符的定义:标准文件描述符:文件描述符的分配: 2. 文件描述符操作打开文件读取文件中的数据 在linux中,socket也被认为是文件的一种ÿ…...
C++面试题记录(网络)
TCP与UDP区别 1. TCP面向连接,UDP无连接,所以UDP数据传输效率更高 2.UDP可以支持一对一、一对多、多对一、多对多通信,TCP只能一对一 3. TCP需要在端系统维护连接状态,包括缓存,序号,确认号,…...

YoloV8改进策略:卷积篇|基于PConv的二次创新|附结构图|性能和精度得到大幅度提高(独家原创)
摘要 在PConv的基础上做了二次创新,创新后的模型不仅在精度和速度上有了质的提升,还可以支持Stride为2的降采样。 改进方法简单高效,需要发论文的同学不要错过! 论文指导 PConv在论文中的描述 论文: 下面我们展示了可以通过利用特征图的冗余来进一步优化成本。如图3所…...

图论(从数据结构的三要素出发)
文章目录 逻辑结构物理结构邻接矩阵定义性能分析性质存在的问题 邻接表定义性能分析存在的问题 十字链表(有向图)定义性能分析 邻接多重表(无向图)定义性能分析 数据的操作图的基本操作图的遍历广度优先遍历(BFS)算法思想和实现性能分析深度优先最小生成…...
spark相关知识
1.Spark的特点 Spark的设计遵循“一个软件栈满足不同应用场景”的理念,逐渐形成了一套完整的生态系统,既能够提供内存计算框架,也可以支持SQL即席查询、实时流式计算、机器学习和图计算等。 运行速度快,易使用,强大的技…...

K8S认证|CKA题库+答案| 12. 查看Pod日志
目录 12、查看Pod日志 CKA v1.29.0模拟系统 下载试用 题目: 开始操作: 1)、切换集群 2)、提取错误日志 3)、验证提取结果 12、查看Pod日志 CKA v1.29.0模拟系统 下载试用 题目: 您必须在以下C…...

【Java SE】 String、StringBuff和StringBuilder
🥰🥰🥰来都来了,不妨点个关注叭! 👉博客主页:欢迎各位大佬!👈 文章目录 1. 字符串不可变性1.1 设计不可变1.2 修改字符串创建新对象1.3 为什么字符串不可变1.4 String类设计不可变的…...

产品经理-需求分析(三)
1. 需求分析 从业务的需要出发,确定业务目的和目标,将业务需求转为产品需求 1.1 业务需求 业务需求 业务动机 业务目标 就是最根本的动机和目标成果,通过这个需求解决特定的问题 1.2 产品需求 产品需求 解决方案 产品结构 产品流程…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...

【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...

jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...

CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?
在现代前端开发中,Utility-First (功能优先) CSS 框架已经成为主流。其中,Tailwind CSS 无疑是市场的领导者和标杆。然而,一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…...

EEG-fNIRS联合成像在跨频率耦合研究中的创新应用
摘要 神经影像技术对医学科学产生了深远的影响,推动了许多神经系统疾病研究的进展并改善了其诊断方法。在此背景下,基于神经血管耦合现象的多模态神经影像方法,通过融合各自优势来提供有关大脑皮层神经活动的互补信息。在这里,本研…...