libevent源码学习(11):超时管理之min_heap
目录
min_heap的定义
向min_heap中添加event
min_heap中event的激活
以下源码均基于libevent-2.0.21-stable。
在前文中,分析了小顶堆min_heap这一数据结构,并提到了Libevent就是利用min_heap来实现定时器的,接下来就分析一下min_heap是如何实现定时器的。
对于每一个需要监听的event,它都对应一个感兴趣的事件,当感兴趣的事件发生时,这个event就激活了。而实际上,往往都需要设置一个超时结构体timeval,这个超时结构体用来告诉内核“用多长时间去监听这个事件发生”,如果超过了这个时间event对应的感兴趣事件还没有发生,那么也会把这个event激活。为每一个event设置监听超时是很有必要的,因为不是所有event都需要永久监听的,当event的数目很多,就会有大量的超时结构体,定时器min_heap就是用来管理所有被监听的event的超时结构体的。
在libevent中,提供了common_timeout+min_heap的方式来进行超时管理,关于common_timeout会在后面文章中分析,这里只说一下min_heap。
min_heap的定义
每一个event_base都对应一个min_heap数据结构,其定义如下:
struct event_base {
......
/** Priority queue of events with timeouts. */
struct min_heap timeheap;
......
};
也就是说,event_base中的所有设置了超时的event都会放在event_base的timeheap这一成员中。
向min_heap中添加event
为event添加一个超时是通过event_add实现的,而在event_add内部实际上是event_add_internal函数,该函数共有三个传入参数,第一个参数是event指针,第二个参数是一个超时结构体timeval,第三个参数用于指明传入的超时结构体是否为绝对时间。如果传入的timeval非空,说明event是需要设置超时的,通过event_add_internal就可以将该event添加到min_heap中,如下所示:
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
int tv_is_absolute)
{
......
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
//如果event设置了超时,并且event所设超时结构体不在time小根堆上,则在time小根堆中预留空间
if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
if (min_heap_reserve(&base->timeheap,
1 + min_heap_size(&base->timeheap)) == -1)
return (-1); /* ENOMEM == errno */
}
/*
* we should change the timeout state only if the previous event
* addition succeeded.
*/
if (res != -1 && tv != NULL) {
......
gettime(base, &now); //获取系统时间
common_timeout = is_common_timeout(tv, base);
if (tv_is_absolute) { //如果是绝对时间 就直接用ev_timeout存储
ev->ev_timeout = *tv;
} else if (common_timeout) {
......
} else {
evutil_timeradd(&now, tv, &ev->ev_timeout); //如果就只是一个普通的相对时间,就直接用系统时间加上超时时长作为超时时间
}
......
event_queue_insert(base, ev, EVLIST_TIMEOUT); //插入到超时队列中
......
}
这里会先判断event的ev_flags,如果当前的ev_flags设置了EVLIST_TIMEOUT,说明此时这个event已经存在于超时队列中了,否则说明这个event不存在与超时队列中,那么就会先在min_heap中预留一个位置,将来用于存放该event。
接下来就会计算超时时间了。需要注意的是,用户在调用 event_add函数时,传入的timeval结构体一般来说都是相对时间,比如说传入3s的超时结构体,那么我们的想法自然是从现在开始的3s,而在libevent中,判断一个事件是否到了超时的时间点,使用的是绝对时间。举个例子,libevent会先找到当前的绝对时间(即从1970年1月1日到目前经过的时间,可以由gettimeofday函数获得),然后用当前的绝对时间加上传入的相对时间3s,得到的时间就是event最终超时的绝对时间。当需要判断这个event是否超时时,就会判断当前时刻的绝对时间是否达到了event的超时绝对时间,如果达到了那么就是超时了。
因此,event_add_internal函数的第三个参数也就是指明了传入的时间是绝对时间还是相对时间,从程序中可以看出来,如果是绝对时间,其含义就是当系统时间达到了这个绝对时间那就说明该事件超时了,因此就直接把传入的绝对直接设置到event的ev_timeout中;而如果传入的不是一个绝对时间,那么就相当于前面举例,会将当前的系统时间加上传入的相对时间参数最终得到的绝对超时时间设置到event的ev_timeout中。
最后一步也是最关键的一步,将设置了ev_timeout的event添加到min_heap中。这一步使用的是event_queue_insert函数,需要注意的是,传入的第三个参数是EVLIST_TIMEOUT,说明这里是把event添加到超时队列中。接着来看看在这种情况下这个函数做了什么:
static void
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
......
switch (queue) {
case EVLIST_INSERTED: ......
case EVLIST_ACTIVE: ......
case EVLIST_TIMEOUT: { //如果是设置超时事件
if (is_common_timeout(&ev->ev_timeout, base)) {
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
insert_common_timeout_inorder(ctl, ev);
} else
min_heap_push(&base->timeheap, ev);
break;
}
default:
event_errx(1, "%s: unknown queue %x", __func__, queue);
}
}
这里对传入的第三个参数进行了判断,由于刚刚说的传入的是EVLIST_TIMEOUT,并且由于设置的只是一个普通的超时时间,因此执行的是case EVLIST_TIMEOUT下的else部分,这一部分实现的功能很简单,就是把event插入到timeheap中。
由此我们也可以看出event添加到timeheap的过程:先通过传入的timeval计算event超时的绝对时间并且将其保存到event的ev_timeout成员中,然后将event插入到其对应的timeheap中,这样就完成了event在定时器min_heap中的添加。
min_heap中event的激活
当你把所有event都设置好相应的感兴趣事件、回调函数等信息,并将其通过event_add添加到定时器中后,就可以执行event_base_dispatch去监听event_base上的所有event了。在该函数中执行的实际上是event_base_loop函数,这也就是事件主循环,在event_base_loop中,会对所有需要监听的事件进行监听,如果有相应事件发生,就会把对应的event激活,而定时器上的event就在其中。在event_base_loop函数中会调用timeout_process函数去处理定时器中超时的event,该函数定义如下:
static void
timeout_process(struct event_base *base) //将所有超时的事件以超时激活类型添加到激活队列中
{
/* Caller must hold lock. */
struct timeval now;
struct event *ev;
if (min_heap_empty(&base->timeheap)) {
return;
}
gettime(base, &now); //获取当前的系统时间
while ((ev = min_heap_top(&base->timeheap))) {
if (evutil_timercmp(&ev->ev_timeout, &now, >)) //比较定时器堆顶的event是否超时,如果没有超时说明定时器中没有event超时
break;
//如果定时器中有事件超时
/* delete this event from the I/O queues */
event_del_internal(ev); //从相关队列中删除该event
event_debug(("timeout_process: call %p",
ev->ev_callback));
event_active_nolock(ev, EV_TIMEOUT, 1); //按超时激活类型将事件添加到激活队列中
}
}
这个函数的作用就是先得到当前的系统绝对时间,然后从定时器堆顶开始,比较堆顶event的ev_timeout与当前系统绝对时间的大小关系,如果前者更大,说明堆顶的event都还没有超时,那么整个定时器中的event都肯定没有超时了,而如果后者更大,则说明当前堆顶的event已经超时,那么就会先调用event_del_internal函数,在该函数中有以下语句:
static inline int
event_del_internal(struct event *ev)
{
......
if (ev->ev_flags & EVLIST_TIMEOUT) { //如果事件在超时队列中
/* NOTE: We never need to notify the main thread because of a
* deleted timeout event: all that could happen if we don't is
* that the dispatch loop might wake up too early. But the
* point of notifying the main thread _is_ to wake up the
* dispatch loop early anyway, so we wouldn't gain anything by
* doing it.
*/
event_queue_remove(base, ev, EVLIST_TIMEOUT);
}
if (ev->ev_flags & EVLIST_ACTIVE) //如果事件已激活
event_queue_remove(base, ev, EVLIST_ACTIVE);
if (ev->ev_flags & EVLIST_INSERTED) { //如果事件已添加
event_queue_remove(base, ev, EVLIST_INSERTED);
if (ev->ev_events & (EV_READ|EV_WRITE))
res = evmap_io_del(base, ev->ev_fd, ev);
else
res = evmap_signal_del(base, (int)ev->ev_fd, ev);
if (res == 1) {
/* evmap says we need to notify the main thread. */
notify = 1;
res = 0;
}
}
......
}
也就相当于是把超时的event从相关队列中删除,比如说在定时器的event的ev_flags中肯定是设置了EVLIST_TIMEOUT的,因此这里就会执行event_queue_remove(base, ev, EVLIST_ACTIVE);在该函数中则会将event从min_heap中删除:
static void
event_queue_remove(struct event_base *base, struct event *ev, int queue)
{
......
switch (queue) {
......
case EVLIST_TIMEOUT:
if (is_common_timeout(&ev->ev_timeout, base)) {
......
} else {
min_heap_erase(&base->timeheap, ev);
}
break;
}
}
也就是说,timeout_process函数会把定时器min_heap中已经超时的event从定时器min_heap中删除,接着又会执行event_active_nolock(ev, EV_TIMEOUT, 1);在该函数中又会调用event_queue_insert(base, ev, EVLIST_ACTIVE);函数来把这个event添加到激活队列中。
通过以上分析可以知道,定时器min_heap中event的激活是通过timeout_process函数实现的,在该函数中会把已经超时的event从定时器中删除,并且把该event添加到激活队列中。接着只需要处理激活队列,就可以执行这些超时的event对应的回调函数。
————————————————
版权声明:本文为CSDN博主「HerofH_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_28114615/article/details/96571308
libevent源码学习(11):超时管理之min_heap的更多相关文章
- libevent源码学习(10):min_heap数据结构解析
min_heap类型定义min_heap函数构造/析构函数及初始化判断event是否在堆顶判断两个event之间超时结构体的大小关系判断堆是否为空及堆大小返回堆顶event分配堆空间堆元素的上浮堆元素 ...
- libevent源码学习
怎么快速学习开源库比如libevent? libevent分析 - sparkliang的专栏 - 博客频道 - CSDN.NET Libevent源码分析 - luotuo44的专栏 - 博客频道 ...
- libevent源码学习(2):内存管理
目录 内存管理函数 函数声明 event-config.h 函数定义 event_mm_malloc_ event_mm_calloc_ event_mm_strdup_ event_mm_reall ...
- libevent源码学习(9):事件event
目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...
- Mybatis源码学习之事务管理(八)
简述 在实际开发中,数据库事务的控制是一件非常重要的工作,本文将学习Mybatis对事务的管理机制.在Mybatis中基于接口 Transaction 将事务分为两种,一种是JdbcTransacti ...
- Python 源码学习之内存管理 -- (转)
Python 的内存管理架构(Objects/obmalloc.c): _____ ______ ______ ________ [ int ] [ dict ] [ list ] ... [ str ...
- libevent源码学习(8):event_signal_map解析
目录event_signal_map结构体向event_signal_map中添加event激活event_signal_map中的event删除event_signal_map中的event以下源码 ...
- libevent源码学习(6):事件处理基础——event_base的创建
目录前言创建默认的event_baseevent_base的配置event_config结构体创建自定义event_base--event_base_new_with_config禁用(避免使用)某一 ...
- libevent源码学习(1):日志及错误处理
目录 错误处理函数 函数声明 __attribute__指令 函数定义 可变参数宏 _warn_helper函数 日志处理 event_log日志处理入口 日志处理回调函数指针log_fn 设置日志处 ...
随机推荐
- C/C++ Qt ListWidget 列表框组件应用
ListWidget列表框组件,该组件与TreeWidget有些相似,区别在于TreeWidget可以实现嵌套以及多字段结构,而ListWidget组件则只能实现单字段结构,ListWidget组件常 ...
- Spring扩展点-v5.3.9
Spring 扩展点 **本人博客网站 **IT小神 www.itxiaoshen.com 官网地址****:https://spring.io/projects/spring-framework T ...
- 【AWS】通过对等网络打通VPC访问
参考 什么是 VPC 对等? - Amazon Virtual Private Cloud 目的 有些服务,比如内网ALB,不公开的RDS仅允许VPC内部访问.如遇到跨账号.跨区域访问,则需要在两个v ...
- Servlet jsp maven 依赖包
servlet<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servl ...
- 洛谷 P4099 - [HEOI2013]SAO(树形 dp)
题面传送门 题意: 有一个有向图 \(G\),其基图是一棵树 求它拓扑序的个数 \(\bmod (10^9+7)\) \(n \in [1,1000]\) 如果你按照拓扑排序的方法来做,那恐怕你已经想 ...
- 【豆科基因组】大豆(Soybean, Glycine max)经典文章梳理2010-2020
目录 2010年1月:大豆基因组首次发表(Nature) 2010年12月:31个大豆基因组重测序(Nature Genetics) 2014年10月:野生大豆泛基因组(Nature Biotechn ...
- Shell中 ##%% 操作变量名
在linxu平台下少不了对变量名的处理,今天记录下shell中 ##%% 对变量名的操作. #操作左侧,%操作右侧. #号处理方式: 对于单个#,处理对象为变量中指定的第一个符号左侧字符串, 对于两个 ...
- RocketMQ这样做,压测后性能提高30%
从官方这边获悉,RocketMQ在4.9.1版本中对消息发送进行了大量的优化,性能提升十分显著,接下来请跟着我一起来欣赏大神们的杰作. 根据RocketMQ4.9.1的更新日志,我们从中提取到关于消息 ...
- 巩固javaweb的第二十六天
正则表达式 正则表达式提供了一种高级的.但不直观的字符串匹配和处理的方法.它描述了一种 字符串匹配的模式,可以用来判断一个字符串是否满足某种格式,或者一个字符串是否含 有某个子串等. 1. 字符集 正 ...
- Slay 全场!Erda 首次亮相 GopherChina 大会
来源|尔达 Erda 公众号 相关视频:https://www.bilibili.com/video/BV1MV411x7Gm 2021 年 6 月 26 日,GopherChina 大会准时亮相北京 ...