博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
微信后台 phxrpc (v0.8) 之 Timer(二)
阅读量:4053 次
发布时间:2019-05-25

本文共 3221 字,大约阅读时间需要 10 分钟。

一.system_clock和steady_clock比较

system_clock:就类似Windows系统右下角那个时钟,是系统时间。明显那个时钟是可以乱设置的。明明是早上10点,却可以设置成下午3点。
steady_clock:则针对system_clock可以随意设置这个缺陷而提出来的,他表示时钟是不能设置的。
steady_clock的实现是使用monotonic时间,而monotonic时间一般是从boot启动后开始计数的。明显这不能获取日历时间(年月日时分秒)。
那么steady_clock有什么用途呢?时间比较!并且是不受用户调整系统时钟影响的时间比较。简单的例子如下:

auto begin = std::chrono::steady_clock::now();for(int i = 0; i < 10000000; ++i){    // 计算...}auto end = std::chrono::steady_clock::now();auto diff = (end - begin).count(); //end-begin得到一个duration类型std::cout<
<

二.定时管理器heap

phx使用了heap(小根堆)来管理超时,一般来说,heap能做的操作是top(), push(), pop()三个操作。但是这里的一些设计要求可以在任何合法的位置删除节点,所以仅仅有pop()是不够的。

看下面一段代码:

int UThreadPoll(UThreadSocket_t & socket, int events, int * revents, int timeout_ms) {    int ret = -1;    // 获得当前正在执行的协程,需要这个协程帮助做点事    socket.uthread_id = socket.scheduler->GetCurrUThread();    // 增加超时事件    socket.event.events = events;    socket.scheduler->AddTimer(&socket, timeout_ms);    // 事件加入epoll    // 理论上讲,AddTimer将这个超时事件加入额外管理的heap定时器管理器就OK了    // 此处为什么还需要加入epoll中进行调度?    // 原因在于,在超时事件还没到达的时候,可能就有事件触发了,使得下面这一轮提前结束!    epoll_ctl(socket.epoll_fd, EPOLL_CTL_ADD, socket.socket, &socket.event);    // 将当前的协程停止,转让CPU给主协程    // 当主协程下次收到这个超时事件的时候会将执行权还给这个协程    // 协程下次还是从当前位置开始执行    socket.scheduler->YieldTask();    // 当超时任务完成,协程继续从此处执行!    // 完成后从epoll中删除定时器    epoll_ctl(socket.epoll_fd, EPOLL_CTL_DEL, socket.socket, &socket.event);    socket.scheduler->RemoveTimer(socket.timer_id);    ...}

上面这段代码将socket加入超时事件中,同时epoll也会去监控这个socket。那么这就可能存在两种情况:

第一:如果谁正常超时事件达到,协程回到当前代码,那么下面的RemoveTimer就相当于是pop()操作,这个是很常规逻辑。
第二:如果在超时之前,epoll就监控到存在事件触发了,那么在超时之前就重新回到这个协程。这个时候节点并没有超时,所以节点可能不是小根堆的根节点。那么删除的是中间的某个节点。这时候之前的pop就不满足要求了,因为pop只能讲根节点弹出。
这里的根本问题不在删除哪个节点,而是我们怎么定位到那个节点,难道要扫描一遍?那样代价有点大。
phx在此处有一个很trick的做法,它将每个socket关联的定时器的index(在vector数组中的下标)保存在socket结构中!!!这样就可以做到O(1)定位。这个有点。。。只能说“6”。

看下具体的UThreadSocket结构:

typedef struct tagUThreadSocket {    UThreadEpollScheduler * scheduler;    int uthread_id;    int epoll_fd;    int socket;    int connect_timeout_ms;    int socket_timeout_ms;    int waited_events;    size_t timer_id;    // 保存这个socket的定时器在heap的数组中的位置(如果存在),方便在堆中的查找    struct epoll_event event;    void * args;} UThreadSocket_t;

这个结构虽然很方便,但是总是觉得怪怪的。。。

OK,找到相应的节点后,那么删除操作就比较简单了。

看下具体的代码:

// 移除一个计时器void Timer :: RemoveTimer(const size_t timer_id) {    if (timer_id == 0) {        return;    }    size_t now_idx = timer_id - 1;    if (now_idx >= timer_heap_.size()) {        return;    }    TimerObj obj = timer_heap_[now_idx];    UThreadSocketSetTimerID(*obj.socket_, 0);    // 当前与最后一个进行交换    // 然后删除最后一个元素    // 最后需要调整堆(up or down)    std::swap(timer_heap_[timer_heap_.size() - 1], timer_heap_[now_idx]);    timer_heap_.pop_back();    if (timer_heap_.empty()) {        return;    }    // 下面执行up 或者 down逻辑    // 这里需要将移除的节点和最后的节点比较大小    // 从而里判断是需要heap_up还是heap_down    if (timer_heap_[now_idx] < obj) {        heap_up(now_idx + 1);    } else if (timer_heap_[now_idx] == obj) {        UThreadSocketSetTimerID(*timer_heap_[now_idx].socket_, now_idx + 1);    } else {        heap_down(now_idx);    } }

在heap_up和heap_down函数中,有一行类似如下:

UThreadSocketSetTimerID(*timer_heap_[now_idx].socket_, now_idx + 1);

这个就是每次调整完节点需要在socket结构中重新设置关联的timer在vector中的位置,Orz…

其他的正常的堆的操作就没什么好说的了。

转载地址:http://doaci.baihongyu.com/

你可能感兴趣的文章
JVM并发机制探讨—内存模型、内存可见性和指令重排序
查看>>
nginx+tomcat+memcached (msm)实现 session同步复制
查看>>
c++模板与泛型编程
查看>>
WAV文件解析
查看>>
WPF中PATH使用AI导出SVG的方法
查看>>
WPF UI&控件免费开源库
查看>>
QT打开项目提示no valid settings file could be found
查看>>
Win10+VS+ESP32环境搭建
查看>>
android 代码实现圆角
查看>>
flutter-解析json
查看>>
android中shader的使用
查看>>
java LinkedList与ArrayList迭代器遍历和for遍历对比
查看>>
drat中构造方法
查看>>
JavaScript的一些基础-数据类型
查看>>
转载一个webview开车指南以及实际项目中的使用
查看>>
ReactNative使用Redux例子
查看>>
Promise的基本使用
查看>>
coursesa课程 Python 3 programming 统计文件有多少单词
查看>>
coursesa课程 Python 3 programming 输出每一行句子的第三个单词
查看>>
Returning a value from a function
查看>>