Anoii网络库之Timer实现
Timer的实现挺值得拿出来聊一聊的
Anoii网络库的事件循环:
Timer是使用poll的timeout参数实现的,所以定时的精度是毫秒,对于一个网络库而言这足够了。如果不够的话,还可以使用timerfd来提升精度。
由于需要计算poll的timeout是多少,就需要组织起来所有的Timer,计算下一次触发timeout的时间。所以我将所有的Timer用std::set组织起来了。具体的说是std::set<mstime_t, Timer*>
,第一个参数是该Timer被触发的时间,第二个参数就是Timer本身。实际上组织Timer的对象就是TimerQueue
使用std::set而不是std::map<mstime_t, Timer*>
的原因是,同一个时间可能存在两个Timer被触发,另一个做法是unordered_map,不过没必要这么做,而且用它的人也少。
难点
实现可以见https://github.com/Afeather2017/anoii/blob/master/src/timer_queue.cc
设计一个Timer的难点是,如何确保其生命周期?
如果一个Timer的生命周期与程序差不多长,那么就不存在这种问题,但是如果一个临时的对象使用Timer呢?
Anoii中用于建立连接的Connector就存在这样的问题:
Connector在建立连接的过程中需要进行重试,每次重试的时间间隔是1s, 2s, 4s, 8s...,这样就必须使用定时器了。但是Connector建立连接的过程中,用户应当要能够随时取消建立连接,这意味Connector必须要能够取消Timer。
总不能Connector释放了,Timer还继续使用Connector吧?那么如何设计一个Timer的取消?
智能指针
最容易想到的方案是,使用智能指针。Connector保留一份Timer的shared_ptr,然后Timer在调用之前检查一下引用计数,如果为1那么这个Timer就没必要继续调用了。反过来Timer也许要保留一份Connector的weak_ptr,在调用之前要升级为shared_ptr再调用。但是由于我确保了Connector和Timer的处理都是在同一个线程的,这种做法似乎不是很必要。
直接释放内存
调用取消的时候马上就释放Timer是不可行的。对于一个多线程程序而言,一个线程调用Timer的取消的时候,另一个线程可能还在使用Timer。虽然可以通过确保二者在同一个线程来解决这个场景的问题,但是如果Timer进行了自取消呢?
设置标志位
所以给Timer设计一个统一的取消方式:Timer在调用回调的时候,根据返回值决定是否释放该Timer。返回为false就释放该Timer,返回为true,则该Timer继续定时。
然后,给Timer添加一个标志位,如果它被取消了,就设置该标志。如果标志被设置了,那么在下一次处理Timer的时候就释放它。设置标志位的好处是,别的对象可以正常取消Timer,一个Timer可以取消另一个Timer,Timer也可以自取消。
取消的操作移交到处理该Timer的线程操作,所以不需要锁也可以正常设置该标志位。
取消操作的幂等性
接下来还有一个问题,Timer的重复取消问题。
如果一个Timer被取消了,也被释放了,那么此时设置标志位就是个问题。
解决这个问题的方法是,在TimerQueue中添加一个std::map<TimerId, Timer*>
,TimerId是一个64位整数,它只会递增。添加Timer的时候往里面添加其Id以及Timer指针,取消的时候检查一下该Id是否还存在,否则就直接返回。
这样一来,取消操作就具有幂等性了
改进设计
由于Timer的回调都能够返回值了,那么下一次定时的长度不妨就直接用这个返回值来做。即如果回调返回的值小于0,就可以释放该定时器,如果大于0,那么下一次定时的时间是当前时间+返回的值。这样一来Connector的逐渐增加定时时长就可以轻易的实现了,这样还可以节省malloc次数
Timer自己都带有标志位了,那么就让标志位为其Id得了。第一个Id的值是1,Id大于0表示该Timer还有效,否则无效且可以被释放。取消的时候设置为-Id,这样也不用担心负数设置的时候出现值溢出(因为整数的负数范围比正数的大1,据说Java有个著名的库,好像是Netty,曾经在这上面踩坑,即-INT_MIN = INT_MIN的问题)
所以最终的Timer如下
class Timer {
Timer(std::function<mstime_t(mstime_t)> cb, TimerId id);
void Cancel();
mstime_t Call(mstime_t mstime);
std::function<mstime_t(mstime_t)> cb_;
TimerId timer_id_;
friend TimerQueue;
};
已经修复的BUG1: 定时器时间错误
在编译安卓的时候,发现chargenserver时不时的会阻塞而没有反应。由于缺乏日志,在安卓上又缺乏调试手段,所以一开始没有找到问题。
发现阻塞问题后,我仔细回想了一下哪些地方可能造成长时间阻塞的。第一个是read没有设置非阻塞,第二个是poll中的timeout值为-1。于是在这些地方加了日志,发现poll的timeout设置为-1。果不其然,发现了timeout为-1。
-1的来源是TimerQueue中的SleepTime():
mstime_t TimerQueue::SleepTime() {
struct timeval tv;
gettimeofday(&tv, NULL);
mstime_t temp = timers_.begin()->first - (tv.tv_sec * 1000 + tv.tv_usec / 1000);
if (temp <= 0) {
return 0;
}
return temp;
}
在temp获取之后,加了日志,发现temp的值为负数。
为什么?后面仔细一想,这个函数的行为应当是这样的才对:
- 如果没有定时,返回-1
- 如果有定时,获取第一个定时器的时间,与当前时间相减,如果是负数,返回0,否则返回相减的值
但是上面代码的行为是:
- 如果没有定时,返回timers_.begin()->first - 当前时间,但是由于没有定时,所以timers_.begin()的值可能是一个垃圾值,所以返回的值无法确定,返回-1也是有可能的
- 如果有定时,返回第一个定时器与当前的差值,如果为负数,返回-1,这会让poll阻塞了,否则返回差值
所以修改很简单:
mstime_t TimerQueue::SleepTime() {
if (timers_.size() == 0) return -1;
struct timeval tv;
gettimeofday(&tv, NULL);
mstime_t temp = timers_.begin()->first - (tv.tv_sec * 1000 + tv.tv_usec / 1000);
if (temp <= 0) {
return 0;
}
return temp;
}
Anoii网络库之Timer实现的更多相关文章
- boost.ASIO-可能是下一代C++标准的网络库
曾几何时,Boost中有一个Socket库,但后来没有了下文,C++社区一直在翘首盼望一个标准网络库的出现,网络上开源的网络库也有不少,例如Apache Portable Runtime就是比较著名的 ...
- Linux多线程服务端编程:使用muduo C++网络库
内容推荐本 书主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread.这 ...
- 公布一个基于 Reactor 模式的 C++ 网络库
公布一个基于 Reactor 模式的 C++ 网络库 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice 2010 Aug 30 本文主要介绍 muduo 网 ...
- Mudo C++网络库第三章学习笔记
多线程服务器的适用场合与常用编程模型 进程间通信与线程同步; 以最简单规范的方式开发功能正确.线程安全的多线程程序; 多线程服务器是指运行在linux操作系统上的独占式网络应用程序; 不考虑分布式存储 ...
- Mudo C++网络库第二章学习笔记
线程同步的精要 并发有两种基本的模型: 一种是message passing(消息传递); 另一种是shared memory(共享内存); 在分布式系统中(有多台物理机需要通信), 运行在多台机器上 ...
- muduo网络库学习笔记(三)TimerQueue定时器队列
目录 muduo网络库学习笔记(三)TimerQueue定时器队列 Linux中的时间函数 timerfd简单使用介绍 timerfd示例 muduo中对timerfd的封装 TimerQueue的结 ...
- libiop网络库数据结构和基础知识
最近朋友推荐,学习了libiop这个网络库,作者封装的很全面,代码很简洁 适合初学者学习基于事件驱动的网络io 先看看iop_def.h, 这里面定义了常用的数据结构 tag_iop_base_t 主 ...
- 网络库libevent、libev、libuv对比
Libevent.libev.libuv三个网络库,都是c语言实现的异步事件库Asynchronousevent library). 异步事件库本质上是提供异步事件通知(Asynchronous Ev ...
- Gevent高并发网络库精解
进程 线程 协程 异步 并发编程(不是并行)目前有四种方式:多进程.多线程.协程和异步. 多进程编程在python中有类似C的os.fork,更高层封装的有multiprocessing标准库 多线程 ...
- Cowboy 开源 WebSocket 网络库
Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...
随机推荐
- golang之验证器validator
快速安装 使用之前,我们先要获取validator这个库. # 第一次安装使用如下命令 go get github.com/go-playground/validator/v10 # 项目中引入包 i ...
- 实现ELF文件解析,支持-h, -S, -s
ELF文件 编译和链接 ELF代表Executable and Linkable Format,是类Unix平台最通用的二进制文件格式.下面三种文件的格式都是ELF. 目标文件.o 动态库文件.so ...
- 基于云主机的ModelArts模型训练实践,让开发环境化繁为简
本文分享自华为云社区<[开发者空间实践]云主机安装Docker并制作自定义镜像在ModelArts平台做模型训练>,作者: 开发者空间小蜜蜂. 1.1 案例介绍 在AI业务开发以及运行的过 ...
- C#调用Python代码的方式(二),以PaddleOCR-GUI为例
前言 前面介绍了在C#中使用Progress类调用Python脚本的方法,但是这种方法在需要频繁调用并且需要进行数据交互的场景效果并不好,因此今天分享的是C#调用Python代码的方式(二):使用py ...
- zz 云原生时代,Java的危与机
https://icyfenix.cn/tricks/2020/java-crisis/qcon.html 另一方面,在微服务的背景下,提倡服务围绕业务能力而非技术来构建应用,不再追求实现上的一致,一 ...
- 德哥的PostgreSQL私房菜
德哥的PostgreSQL私房菜 - 史上最屌PG资料合集-博客-云栖社区-阿里云 : https://yq.aliyun.com/articles/59251 https://github.com/ ...
- 基于SpringMVC XML配置文件的Dubbo开发与使用
[模块一] 首先引入Dubbo的依赖资源,这里我们使用基于SpringMVC的项目于Dubbo进行整合 先进行依赖导入. pom.xml <!--zookeeper--> ...
- 【C#】【ffmpeg】外部调用线程执行ffmepg读取返回的信息乱码问题
起因 C#使用FFmpeg获取电脑音视频可以用设备,当返回内容包含中文时,出现乱码问题 解决方案 ffmpeg本身的输出都是使用的错误输出,所以设置的是StandardErrorEncoding,如果 ...
- Write failed: Broken pipe > Couldn‘t read packet: Connection reset by peer SFTP服务器连接出现的问题
如果你链接服务器的时候出现下面的提示: Write failed: Broken pipeCouldn't read packet: Connection reset by peer这个问题的原因是C ...
- Qt数据库应用7-导出打印QTableWidget/QTableView数据
一.前言 本组件的初衷就是造一个轮子,让数据导入导出用法极致简单,几个行数几行代码搞定它,适用大部分的应用场景,这也是本组件和qtxls最大的区别,qtxls的目标是大而全,提供各种xls的接口,至于 ...