高性能定时器概述(包含时间堆的实现)

网络编程处理的事件主要有I/O,信号和定时器!!!

   其中第三类事件就是定时事件,比如:定期检测一个客户连接的活动状态.server通常管理着众多的定时事件,因此有效的管理这些事件,使之能在预期的时间点触发且不影响server的主要逻辑,对于server来讲,至关重要.那么如何来解决这个问题呐?

   我们可以将每个定时事件封装成定时器,并使用某种容器类数据结构,比如:链表,排序链表,时间轮,时间堆来实现将所有的定时器串联起来,实现对定时事件的统一管理.

各个实现方式比较如下(摘自欢神讲座):

实现方式 AddTimer DelTimer ToDoTimer
基于链表   O(1)   O(n)   O(n)  
基于排序链表   O(n)   O(1)   O(1)  
基于最小堆   O(lgn)   O(1)   O(1)  
基于时间轮   O(1)   O(1)   O(1)(前提是使用多个轮子来实现,不过单个轮子也要比O(n)快得多)  

一般情况下最小堆(或者红黑树)的定时器已经足够用了。如果在需要创建很多定时器的场景下,比如异步客户端,大量并发请求发出去的情况下,每个请求都需要一个 Timer 做超时处理,这样的场景,时间轮 (Timing-Wheel) 是最好的选择(Emmm,1987年提出来的) 。

时间轮实现方式比较多,可以很简单也可以很复杂。
io.netty.util.HashedWheelTimer 是个不错的实现。
如果不需要特别多的定时器,C++的话大多数情况下,下面的实现足够了:

typedef std::multimap<timer_key_t, timer_value_t> timer_map_t;

还可以配合 timerfd 把时间处理统一到事件循环里面去 (since Linux 2.6.27)

关于链表的定时器可能用的会比较少,因为毕竟效率有点低下,不过还是要分具体情况具体对待,比如redis就用的是链表来实现的定时器,可是人家还是那么强.

在本文,我们主要讨论的是两种比较高效的管理定时器的容器:时间堆和时间轮

首先,在介绍如何实现定时器之前,我们先来说一下定时的方法(定时是指在一段时间后触发某段代码的机制)主要有:

socket选项SO_RCVTIMEO 和 SO_SNDTIMEO

SIGALRM 信号

I/O 复用系统调用的超时参数

socket选项SO_RCVTIMEO 和 SO_SNDTIMEO #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> /*专门用来设置socket文件描述符属性的函数*/ int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

第三个参数optname可用来指定超时接受(SO_RCVTIMEO)或者超时发送(SO_SNDTIMEO),与其关联的第四个参数此时为timeout类型,指定具体的超时时间。如果是connect()函数,超时对应的errno会是EINPROGRESS。检测到此errno则关闭连接。这就是根据系统调用的返回值来判断超时时间是否已到,据此处理定时任务即关闭连接这两个选项分别用来设置socket接收数据和发送数据的超时时间,因此仅对于数据接收和发送相关的socket专用系统调用有效,这些系统调用包括

send,sendmsg,recv,recvmsg ,accept 和connect.

几个函数的返回值分别是:

在这里插入图片描述

示例代码:

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int timeout_connect(const char *ip, int port, int time) // 5 { int ret = 0; struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); address.sin_port = htons(port); int sockfd = socket(PF_INET, SOCK_STREAM, 0); assert(sockfd >= 0); struct timeval timeout; timeout.tv_sec = time; timeout.tv_usec = 0; socklen_t len = sizeof(timeout); ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len); assert(ret != -1); ret = connect(sockfd, (struct sockaddr *)&address, sizeof(address)); if (ret == -1) { if (errno == EINPROGRESS) { printf("connecting timeout\n"); return -1; } printf("error occur when connecting to server\n"); return -1; } return sockfd; } int main(int argc, char *argv[]) { if (argc <= 2) { printf("usage: %s ip_address port_number\n", basename(argv[0])); return 1; } const char *ip = argv[1]; int port = atoi(argv[2]); int sockfd = timeout_connect(ip, port, 5); if (sockfd < 0) { return 1; } return 0; }

测试见:https://blog.csdn.net/liushengxi_root/article/details/86062661

SIGALRM 信号 I/O 复用系统调用的超时参数

时间轮的实现原理:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwjsjx.html