Libuv 各个回调(异步)事件的调用时机

news/2024/4/28 19:57:36/文章来源:https://blog.csdn.net/qq135595696/article/details/127623368

Libuv 各个回调(异步)事件的调用时机

uv_close、uv_timer_start

  uv_close中注册的回调事件(close_cb)查阅官网API文档,Handle句柄是调用uv_close便会立即关闭,而注册的回调事件将推迟到下一次Loop循环中执行。

  下面有一个应用场景,与uv_close的调用时机有关,服务端有一个心跳检测机制,判断连接上来每个会话(Session)是否具有活性。如下:

bool TcpSessionMgr::ClientHeartCheck() {TcpSession* pTcpSession = nullptr;NET_UV_LOG(NET_UV_LOG_TYPE::NET_UV_LOG_INFO,"ClientHeartCheck...\n");				//日志for (auto iter = m_sessionMaps.begin();iter != m_sessionMaps.end(); iter++) {pTcpSession = iter->second;if (!pTcpSession->GetActive()) {NET_UV_LOG(NET_UV_LOG_TYPE::NET_UV_LOG_INFO,"client stop heartbeat, sessionID:%d\n",pTcpSession->GetSessionID());pTcpSession->DisConnect();			//m_sessionMaps不是立即删除当前没有活性的session,而是在DisConnect中的异步回调事件(OnSessionClose)中删除。方法定义均在下方。}pTcpSession->SetActive(false);}return true;
}bool TcpSession::DisConnect() {ShutDownHandle((uv_handle_t*)&m_uvTcp, OnSessionClose);return true;
}bool ShutDownHandle(uv_handle_t* handle, uv_close_cb closeCB) {bool result = false;if (0 != uv_is_closing(handle)) {NET_UV_LOG(NET_UV_LOG_TYPE::NET_UV_LOG_ERROR, "handle already close or closing...\n");goto Exit;}uv_close(handle, closeCB);result = true;
Exit:return result;
}void OnSessionClose(uv_handle_t* handle) {TcpSession* pTcpSession = (TcpSession*)handle->data;assert(NULL != pTcpSession);NET_UV_LOG(NET_UV_LOG_TYPE::NET_UV_LOG_INFO, "client closed : %llu\n",(uint64_t)handle);pTcpSession->SetActive(false);//通知逻辑层关闭连接pTcpSession->NotifyRemoveSession();		//在这里通知m_sessionMaps进行删除。//释放pTcpSessionFree(pTcpSession);
}

  其中,ClientHeartCheck是一个定时器事件,定时时间为3000。

m_timerID = m_service->RegisterTimer(std::bind(&TcpSessionMgr::ClientHeartCheck, &m_tcpSessionMgr), 3000);//注册定时器,定时时间为3000,即3s执行一次。

  在这个过程中,出现问题即原因为:当在ClientHeartCheck方法中pTcpSession->DisConnect();处断点调试,经过3s后,ClientHeartCheck方法已经执行一次,但是OnSessionClose回调事件在第一次并未执行,而是在ClientHeartCheck方法执行第二次时,OnSessionClose回调事件才执行。原因文章开头已经说明,由于添加断点调试时间过长,而uv_close注册的回调事件只有loop第二次循环才执行,断点结束时,导致该定时器已经再次超时,而我们观察uv_run的源码会发现定时器的缓冲队列是在整个uv_run开头执行(uv__run_timers),而控制uv_close的回调事件(uv_process_endgames)是在定时器缓冲队列之后处理的,所以导致我们没来得及删除session,却已经遍历第二次m_sessionMaps容器。

int uv_run(uv_loop_t *loop, uv_run_mode mode) {DWORD timeout;int r;int ran_pending;r = uv__loop_alive(loop);if (!r)uv_update_time(loop);while (r != 0 && loop->stop_flag == 0) {uv_update_time(loop);//执行计时器超时事件uv__run_timers(loop);		//这里处理定时器超时事件ran_pending = uv_process_reqs(loop);uv_idle_invoke(loop);uv_prepare_invoke(loop);timeout = 0;if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)timeout = uv_backend_timeout(loop);if (pGetQueuedCompletionStatusEx)uv__poll(loop, timeout);elseuv__poll_wine(loop, timeout);/* Run one final update on the provider_idle_time in case uv__poll** returned because the timeout expired, but no events were received. This* call will be ignored if the provider_entry_time was either never set (if* the timeout == 0) or was already updated b/c an event was received.*/uv__metrics_update_idle_time(loop);uv_check_invoke(loop);uv_process_endgames(loop);		//这里处理uv_close的回调事件if (mode == UV_RUN_ONCE) {/* UV_RUN_ONCE implies forward progress: at least one callback must have* been invoked when it returns. uv__io_poll() can return without doing* I/O (meaning: no callbacks) when its timeout expires - which means we* have pending timers that satisfy the forward progress constraint.** UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from* the check.*/uv__run_timers(loop);}r = uv__loop_alive(loop);if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)break;}/* The if statement lets the compiler compile it to a conditional store.* Avoids dirtying a cache line.*/if (loop->stop_flag != 0)loop->stop_flag = 0;return r;
}

  在下上述结论之前,博主尝试过将ClientHeartCheck的定时器间隔时间调整至1000(整个Loop循环执行一次时间为1000以上,即1s以上,博主在idle里面Sleep了一秒),那么按照原理,即使没有添加断点调试,m_sessionMaps仍然会遍历两次(即ClientHeartCheck执行两次),经过博主的实操验证,确实如此。

  在这个过程中,博主有想过是否uv_close调用后,未执行下次定时事件之前就会调用uv_close的回调事件,但实际上并不会,uv_close的回调事件libuv规定是下次Loop循环时调用。

  博主还考虑过是否uv_run中uv__run_timers方法会执行死循环,即执行多次(由于我们添加断点调试,那么定时器一直在超时,如果是死循环,那么会一直调用),但是后来测试(尝试第二次执行ClientHeartCheck后继续加断点,一段时间后运行),发现只会调用两次,不会有第三次,这也印证了uv_close的回调事件在Loop循环的下一次是会执行的。同时博主也通过查看源代码,发现同一个定时器事件,在一次uv__run_timers是不可能执行两次的,原因如下:

void uv__run_timers(uv_loop_t* loop) {struct heap_node* heap_node;uv_timer_t* handle;for (;;) {	//死循环//小顶堆结构,获取超时的定时器heap_node = heap_min(timer_heap(loop));if (heap_node == NULL)break;handle = container_of(heap_node, uv_timer_t, heap_node);//如果当前节点的时间(由于是小顶堆,已经是时间历时最久的节点)大于loop当前时间则返回if (handle->timeout > loop->time)break;//移除该定时器节点,如果设置repeat则会重新插入到小顶堆中,博主的封装的定时器默认设置repeatuv_timer_stop(handle);uv_timer_again(handle);//执行超时回调事件handle->timer_cb(handle);}
}

  一开始总是恍惚的,既然重新添加到小顶堆中,而且是死循环,再加上我们添加断点调试(人为超时),那么不是会一直超时,一直执行,不也就可以说明为什么ClientHeartCheck调用第二次了吗?但是忽略了一个非常关键的点,那就是loop->time在当前整个uv__run_timers方法中是从而更新过的,那么真的会像Windows系统时间那样一直往前吗?这显然是不会的。

  经过上述问题,可以发现,在使用Libuv开发的过程中,我们不要认为注册回调事件就一定按照心中所想调用,应当搞清楚,我们注册进去的回调事件具体是何时调用的,才是最重要的。

  下面顺带将常用的回调(异步)事件的调用时机进行梳理一遍。

uv_idle_start

  该事件每次Loop循环都会执行一次其回调事件。注意,它的回调执行时机比Prepare Handles要早。

uv_prepare_start

  该事件每次Loop循环都会执行一次其回调事件。注意,它的回调执行时机比Idles Handles要晚,但是在IO轮询之前执行。

uv_check_start

  该事件每次Loop循环都会执行一次其回调事件。注意,它的回调执行时机在IO轮询之后执行。

效果图

  正常情况下,所有的I/O回调都在轮询I/O后立刻被调用。但是有些情况下,回调事件可能被推迟至下一次循环迭代中再执行。任何上一次循环中被推迟的回调事件,都会在Pending callbacks这个时候被执行。

  close callbacks,如果一个句柄通过uv_close()关闭,那么都会在这个时候被调用(注意是下次迭代调用)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_410195.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式——创建型模式

五大-创建型模式一、单例模式1、简介2、单例模式八种方式2.1、饿汉式(静态常量)2.2、饿汉式(静态代码块)2.3、懒汉式(线程不安全)2.4、懒汉式(线程安全,加同步方法)2.5、…

C2 实验 学习笔记

C2 实验 免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关. C2隐藏技术 CDN 准备 一台 vultr centos7 机器一个域名cloudflare 账号 挂上 cdn 在域名购买后配置,cf 中的域名解析,在 cf 中配置…

「MySQL高级篇」MySQL之MVCC实现原理事务隔离级别的实现

①MVCC定义,用处,快照读,当前读 ②MVCC实现原理:隐藏字段,readview,undo log ③readview访问规则 ④事务隔离级别的具体实现大家好,我是melo,一名大三后台练习生,死去的MVCC突然开始拷打我🤣🤣🤣!🍳引言 MVCC,非常顺口的一个词,翻译起来却不是特别顺口:多…

Fiddler 抓包工具

1 基本使用 官网下载地址:Download Fiddler Web Debugging Tool for Free by Telerik X.1 电脑端监听 我们双击打开软件,进入到如下的一个界面,然后点击某一个请求,你会发现请求的内容是一堆明显不对的文字,然后该请求…

MySQL性能优化和慢查询日志

目标 了解性能优化的方案能够使用慢日志定位慢SQL 讲解 1. 优化方案 1.1 为什么要优化数据库性能 ​ MySQL凭借着出色的性能、低廉的成本、丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库。可以看到Google,Facebook,Twitter&…

【百度地图】百度地图的使用方法 和 在vue中如何使用百度地图(超详细)

【百度地图】百度地图的使用方法 和 在vue中如何使用百度地图(超详细) 1- 介绍 百度地图功能强大,本篇文章只是对百度地图JavaScript API 进行一个介绍~ 官方网址 百度地图开放平台LBS:LocationBusinessServer 基于定义位置的商…

Spark 离线开发框架设计与实现

一、背景 随着 Spark 以及其社区的不断发展,Spark 本身技术也在不断成熟,Spark 在技术架构和性能上的优势越来越明显,目前大多数公司在大数据处理中都倾向使用 Spark。Spark 支持多种语言的开发,如 Scala、Java、Sql、Python 等。…

Matlab神经网络函数newff()新旧用法差异

在Matlab R2010a版中,如果要创建一个具有两个隐含层、且神经元数分别为5、3的前向BP网络,使用旧的语法可以这样写:net1 = newff(minmax(P), [5 3 1]); 注意minmax()函数的使用,还有对输出层神经元数(1)的指定。当然也可以采用新的语法,更简洁(请留意差异):net2 = new…

形态分类行为中的气泡佯谬

“假设光归根结底是波,只是给我们以粒子的印象,因为粒子吸收光波的能量是以离散的包的方式。波从源头传播出去像一个越来越大正在膨胀的气泡,到达一个原子时,气泡破裂,波坍缩并把所有的能量集中在一个地方,…

【数字式时间继电器】TR-23 DC110V

系列型号 TR-20数字式时间继电器;TR-21数字式时间继电器; TR-22数字式时间继电器;TR-23数字式时间继电器; TR-24数字式时间继电器;TR-25数字式时间继电器; TR-20D数字式时间继电器;TR-21D数字式…

无刷电机控制基础(3)——FOC矢量控制入门

本节我们讲一些无刷电机FOC矢量控制的入门知识。 1)FOC矢量控制的作用 我们前两节讲的无刷电机(BLDC),是最简单的结构,当转子匀速转动时,定子内产生的反电动势是梯形波;在驱动无刷电机转动时&a…

你不知道的JavaScript-----强制类型转换

目录 值类型转换 抽象值的操作 JSON 字符串化 ToNumber: 非数字值到数字值 Number(value) ToBoolean: 转换为布尔类型 Boolean(value) 强制类型转换 字符串和数字之间的显式强制类型转换 奇特的~运算符 字位截除 显式解析数字字符串 显式转换为布尔值 隐…

Mybatis查询返回结果类型专题

文章目录一、返回一条信息二、返回List集合三、返回Map集合四、返回多个Map集合五、返回List集合一、返回一条信息 Student selectById(Long id); 不再赘述 二、返回List集合 List< Student> selectAll(); 不再赘述 三、返回Map集合 用map集合去接收返回来的结果 字…

Python-- list(列表)的使用

目录 1.合并两个有序序列构成一个有序列表 2.编写程序判断列表是否为升序 3.输入一个十进制转换为二进制输出 4.将列表中的前p个元素到尾列表 1.合并两个有序序列构成一个有序列表 代码如下&#xff1a; list1 list(eval(input("请输入有序列表list1:"))) list…

【飞桨PaddleSpeech语音技术课程】— 一句话语音合成全流程实践

(以下内容搬运自飞桨PaddleSpeech语音技术课程&#xff0c;点击链接可直接运行源码) 一句话语音合成全流程实践 点击播放视频 1 声音克隆介绍 & 语音合成基本概念回顾 语音合成&#xff08;Speech Sysnthesis&#xff09;&#xff0c;又称文本转语音&#xff08;Text-t…

Web前端:angular对比React——选择2022年Web开发的理想框架

Javascript世界中的框架列表不断增长和变化&#xff0c;但有两个框架从其他框架中脱颖而出。Angular和React是市场上最受欢迎的框架之一&#xff0c;代表了创建web应用程序和网站的两种不同方法。 试图利用web开发框架的开发人员和企业家现在正在分析Angular和React——这两种方…

软考下午题第2题——E-R图 UML图 逻辑结构设计-示题与解析

下午的第二题主要是找【属性】【主键】【外键】【候选键】之间的关系。 候选键&#xff1a;属性或者是属性组合&#xff0c;其值能够唯一地标识一个元组 主键&#xff1a;在一个关系中可能有多个候选键&#xff0c;从中选择一个作为主键 外键&#xff1a;如果一个关系中的属性或…

微机期末复习指导

目录 位扩展定义字扩展定义1、线选法定义优点缺点2、部分译码法定义3、全译码法定义优点缺点⭐字位扩展定义问题

高压放大器基于声纹影法的声可视化实验的应用

实验名称&#xff1a;高压功率放大器基于声纹影法的声可视化实验应用 研究方向&#xff1a;声学超表面声学隐身斗篷 实验内容&#xff1a;利用声纹影平台&#xff0c;对所设计的声隐身斗篷进行出射平面波的测量&#xff0c;采用安泰放大器来驱动平面超声波阵列&#xff0c;可以…

CSS3专题-[上篇]:过渡、2D转换、动画

目录 CSS3&#xff1a;前置特性 CSS3&#xff1a;盒子模型 CSS3&#xff1a;图片滤镜与模糊处理 blur()&#xff1a;高斯模糊 CSS3&#xff1a;计算盒子宽度calc()函数 CSS3&#xff1a;过渡效果 transition属性 2D转换&#xff1a;transform属性 translate()方法 * t…