本文共 3221 字,大约阅读时间需要 10 分钟。
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<<
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/