[原]利用最小堆管理事件超时
引言:前面的一系列文章都在说了事件模型,也就是简单的做一个介绍,然后贴出了一些代码作为Demo,上次说到了在反应堆中的超时管理。今天就来说说关于利用最小堆来管理超时的问题。
NOTICE:判断一个事件是否超时的方法是, 事件超时时间 减 当前时间 ,如果大于零,说明没有超时,如果小于零,说明该事件超时了。
一般做法是怎样来管理所有的超时事件呢?以前的用法都是利用链表来保存所有的超时事件,轮训查看是否事件超时,若超时就采取相应的措施,比如移除事件等。后来的Nginx和libevent采取了更好的措施,Nginx是利用红黑树来管理,而libevent是利用最小堆来管理。
今天的主题是最小堆,所以下面的内容都是围绕最小堆来展开。
这里说说最小堆的好处,然后接着说明为什么要利用最小堆来管理超时事件。
最小堆的好处:
(1)最小堆的设计简单,易于实现
(2)插入和删除操作都是在log(n)基础上完成
(3)获取最小值是O(1)
为什么要利用最小堆:
大家都知道在IO多路复用中比如 select() 和 epoll() 都有一个超时的最大时间,也就是说,如果没有监听到事件,最大的阻塞时间就是这参数。
当我们在Reactor中加入了一个超时事件的时候,我们就利用这个最小堆中的根节点的时间减去当前时间作为select/epoll超时的最大值。所以,当select/epoll返回的时候得到函数执行的消耗的时间,查看最小堆的根是否超时(NOTICE),如果没有超时,我们继续正常步骤,如果超时就采取相应措施,比如超时事件的回调函数等,然后继续查看新的堆结构的根节点,直到处理不超时的情况出现,因为堆是自适应的,所以根部必然是值最小的,如果这个事件没有超时,就可以停止超时事件的处理 了。
所以总体来看,每次都会找到最小堆中最小的元素作为比较,事件复杂度是O(1)
最小堆的性质:
所有父节点总是小于或等于所有的子节点(递归定义),看做二叉树的话是完全二叉树
看看最小堆的实现:
结构表示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | typedef struct heap_node_s { int index; int key ; }mc_minheap_node_t ; typedef struct min_heap_s { mc_minheap_node_t *node_list ; int headindex ; int lastindex ; size_t max_num ; }mc_minheap_t; |
几个宏:
#define MAX_INT 0x0fffffff #define PARENT(i) ((i)/(2)) #define LEFTCHILD(i) ((i)*(2)) #define RIGHTCHILD(i) (((i)*(2))+(1)) |
插入:
每次插入到堆的末尾,然后逐层比较,直到大于父节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | int mc_minheap_insert( mc_minheap_t *mh , int key ) { if ( mh == NULL ) return -1; int lastindex = mh->lastindex; mh->node_list[lastindex].key = key ; mh->node_list[lastindex].index = lastindex ; int tindex = lastindex ; //heap top if ( lastindex == 1 ) { mh->lastindex++ ; return 1; } else { if ( lastindex >= mh->max_num -1 ) return -1; while ( tindex != 1 ) { if ( key < mh->node_list[PARENT( tindex )].key ) { swap_node( &(mh->node_list[tindex]), &(mh->node_list[PARENT( tindex )]) ); } tindex = PARENT( tindex ); } } mh->lastindex++ ; return 1; } |
删除:
删除的时候,把需要删除的节点与堆的最后一个节点交换,删除这个节点。然后交换后的这个节点与子节点中较小的一个比较,如果大于它就交换,如果小于就结束,直到没有子节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | int mc_minheap_rm( mc_minheap_t *mh ) { if ( mh == NULL ) return -1; int ret = mh->node_list[1].key ; mh->node_list[1].key = MAX_INT ; mc_minheap_node_t * pnode = &(mh->node_list[1]) ; mc_minheap_node_t *minnode ; swap_node( pnode , &(mh->node_list[mh->lastindex])); int i = 1 ; for ( ; i < mh->lastindex ; i++ ) { minnode = mh->node_list[LEFTCHILD(i)].key <= mh->node_list[RIGHTCHILD(i)].key ? &(mh->node_list[LEFTCHILD(i)]) : &(mh->node_list[RIGHTCHILD(i)]); if ( i == mh->max_num -1 ) break ; if ( minnode->key == MAX_INT ) break ; if ( pnode.key > minnode.key ) swap_node( pnode , minnode ); else break ; pnode = minnode ; } mh->lastindex-- ; return ret ; } int mc_minheap_rm_index( mc_minheap_t *mh , int index ) { if ( mh == NULL ) return -1; if ( index > mh->lastindex ) return -1; int ret = mh->node_list[index].key ; mh->node_list[index].key = MAX_INT ; mc_minheap_node_t * pnode = &(mh->node_list[index]) ; mc_minheap_node_t *minnode ; swap_node( pnode , &(mh->node_list[mh->lastindex])); int i = index ; for ( ; i < mh->lastindex ; i++ ) { minnode = mh->node_list[LEFTCHILD(i)].key <= mh->node_list[RIGHTCHILD(i)].key ? &(mh->node_list[LEFTCHILD(i)]) : &(mh->node_list[RIGHTCHILD(i)]); if ( i == mh->max_num -1 ) break ; if ( minnode->key == MAX_INT ) break ; if ( pnode.key > minnode.key ) swap_node( pnode , minnode ); else break ; pnode = minnode ; } mh->lastindex-- ; return ret ; } |
初始化和交换节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | mc_minheap_t * mc_minheap_ini( int nodenum ) { int i = 0 ; if ( nodenum <= 0 ) return NULL; mc_minheap_t * mh = ( mc_minheap_t *) malloc ( sizeof ( mc_minheap_t )); if ( mh == NULL ) { return NULL; } mh->node_list = (mc_minheap_node_t *) malloc ( sizeof (mc_minheap_node_t )*nodenum ); mh->headindex = 1 ; mh->lastindex= 1 ; for ( i = 0 ; i < nodenum ; i++ ) { mh->node_list[i].key = MAX_INT ; mh->node_list[i].index = i+1; } mh->max_num = nodenum ; return mh; } static int swap_node( mc_minheap_node_t * n1 , mc_minheap_node_t *n2 ) { if ( n1 == NULL || n2 == NULL ) return -1; mc_minheap_node_t temp; temp.key = n1->key ; n1->key = n2->key; n2->key = temp.key ; } |
总结:最小堆的设计方式比较简单,但是功能不错,有一个缺点就是初始化的时候需要固定大小的节点个数,如果超时事件过多,需要采取一定的措施来保证堆的代码质量。或许可以采用多个对的方式,然后比较每一个堆的最小值,这样也就是O(N)复杂度,N= 堆的个数,这里是我的一厢情愿罢了..
文章如有错误请指正,我会在收到留言的第一时间修改。欢迎交流
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 你所不知道的 C/C++ 宏知识
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· SQL Server 内存占用高分析
· .NET Core GC计划阶段(plan_phase)底层原理浅谈
· .NET开发智能桌面机器人:用.NET IoT库编写驱动控制两个屏幕
· 我干了两个月的大项目,开源了!
· 千万级的大表,如何做性能调优?
· 推荐一款非常好用的在线 SSH 管理工具
· 盘点!HelloGitHub 年度热门开源项目
· Phi小模型开发教程:用C#开发本地部署AI聊天工具,只需CPU,不需要GPU,3G内存就可以运行,