服务器程序通常需要处理众多定时事件,如何有效地组织与管理这些定时事件对服务器的性能至关重要。为此,我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构,比如链表、排序链表和时间轮,将所有定时器串联起来,以实现对定时事件的统一管理
每个定时器通常至少包含一个超时时间和一个任务回调函数。此外,定时器还可以包括回调函数参数及是否自动重启等信息
通常有两种高效管理定时器的容器:时间轮和时间堆
https://github.com/huxiaohei/tiger.git
tiger
中的定时器采用最小堆设计,所有定时器根据绝对的超时时间点进行排序,每次取出离当前时间最近的一个超时时间点,计算出超时需要等待的时间,然后等待超时。超时时间到后,获取当前的绝对时间点,然后把最小堆里超时时间点小于这个时间点的定时器都收集起来,执行它们的回调函数
定时器虽然采用绝对事件排序,但是为了开发同学使用方便,一般接口都是提供相对时间,比如相对当前时间3
秒后执行。会根据传入的相对时间和当前的绝对时间计算出定时器超时时的绝对时间点
tiger
定时器只支持毫秒级,因为定时器的超时等待是基于epoll_wait
实现,epoll_wait
的超时精度也只有毫秒级
首先,我们在TimerManager
类中定义了一个Timer
类。我们将每一个定时事件所需要的信息都封装到Timer
中,然后使用TimerManager
管理所有的Timer
。因此所有Timer
对象都应该通过TimerManager
类提供的接口来操作
TimerManager
类提供了添加、取消、重置、判断、取消所有等操作接口
Timer::ptr add_timer(time_t interval, std::function<void()> cb, bool loop = false);
Timer::ptr add_timer(time_t interval, std::function<void()> *cb, bool loop = false);
Timer::ptr add_cond_timer(time_t interval, std::function<void()> cb, std::weak_ptr<void> cond, bool loop = false);
Timer::ptr add_cond_timer(time_t interval, std::function<void()> *cb, std::weak_ptr<void> cond, bool loop = false);
bool is_valid_timer(Timer::ptr timer);
void reset_timer(Timer::ptr timer, time_t interval);
bool cancel_timer(Timer::ptr timer);
void cancel_all_timer();
其中添加一个条件定时器时需要指定一个条件变量cond
,在定时器被触发时,会先判断cond
是否为真。如果cond
为真,则执行对应回调函数,否则放弃执行
TimerManager
将每个Timer
对象存储在属性m_timers
中,m_timers
的类型为std::set
。因此,m_timers
中的所有Timer
对象的都是按照回调绝对时间从近到远排序好的
上面提到定时器的超时等待是基于epoll_wait
实现,因此我们将TimerManager
融合到IO
协程调度器中。这样既能利用IO
协程调度器中封装好的epoll
完成定时器事件的触发,又能隐藏定时器的实现细节,降低开发者对细节的理解成本
因此我们直接让IO
协程调度器继承TimerManager
类,此时IO
协程调度器就具备了添加、取消、重置、判断一个定时器。也具备取消所有定时器的功能。然后在IO
协程调度器一旦进入idle
状态(即idle
协程开始执行),程序就会被阻塞在epoll_wait
上,因此我们将epoll_wait
的超时时间设置为m_timers
中的第一个元素的超时时间(需要转换为相对时间)。等到epoll_wait
返回,我们获取m_timers
中所有已经超时的Timer
,然后将Timer
中保存的回调函数加入到IO
调度器等待执行
这里我们考虑一个问题,当我们添加一个定时器,此时定时器排在m_timers
中的第一位,但epoll_wait
的超时时间为之前排在第一位的定时器的超时时间。因此,当m_timers
中的第一位元素发生变化的时候,我们需要更改定时器epoll_wait
的超时时间
为此,添加一个Timer
到m_timers
之后,需要检测第一位是否发生变化,即m_timers
中第一个元素是否为刚添加的Timer
对象。如果发生变化应该更新epoll_wait
的超时时间,因此我们调用TimerManager
中的一个虚方法on_timer_refresh
来通知需要更新epoll_wait
的超时时间
TimerManager::Timer::ptr TimerManager::add_timer(time_t interval, std::function<void()> cb, bool loop) {
auto timer = std::make_shared<Timer>(interval, loop, cb);
ReadWriteLock::WriteLock lock(m_lock);
m_timers.insert(timer);
if (m_timers.begin() == m_timers.find(timer)) {
on_timer_refresh();
}
return timer;
}
在IO
协程调度器中,我们重载了on_timer_refresh
方法。此方法在IO
协程调度器中只是简单的调用了tickle
方法,如果此时IO
协程调度器中有线程处于idle
状态,那么它就会被唤醒,等到再次进入idle
状态就会获取最近的超时时间作为epoll_wait
的超时时间
void IOManager::tickle() {
if (has_idel_thread()) {
int rt = write(m_tickle_fds[1], "T", 1);
TIGER_ASSERT_WITH_INFO(rt == 1, "[write fail]");
}
}
void IOManager::idle() {
epoll_event *events = new epoll_event[256]();
int rt = 0;
std::vector<std::function<void()>> cbs;
while (true) {
rt = 0;
do {
static const int EPOLL_MAX_TIMEOUT = 5000;
time_t next_time = next_timer_left_time();
rt = epoll_wait(m_epfd, events, 256, next_time > EPOLL_MAX_TIMEOUT ? EPOLL_MAX_TIMEOUT : (int)next_time);
if (rt > 0 || next_timer_left_time() <= 0 || is_stopping()) break;
} while (true);
all_expired_cbs(cbs);
schedules(cbs.begin(), cbs.end());
cbs.clear();
for (int i = 0; i < rt; ++i) {
epoll_event &event = events[i];
if (event.data.fd == m_tickle_fds[0]) {
uint8_t dummy;
while (read(m_tickle_fds[0], &dummy, 1) == 1)
continue;
continue;
}
Context *ctx = (Context *)event.data.ptr;
MutexLock::Lock lock(ctx->mutex);
if (event.events & (EPOLLERR | EPOLLHUP)) {
event.events |= EPOLLIN | EPOLLOUT;
}
EventStatus real_status = EventStatus::NONE;
if (event.events & EPOLLIN) {
real_status = EventStatus::READ;
}
if (event.events & EPOLLOUT) {
real_status = EventStatus::WRITE;
}
if ((EventStatus)(ctx->statuses & real_status) == EventStatus::NONE)
continue;
EventStatus left_status = (EventStatus)(ctx->statuses & ~real_status);
int op = left_status ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
event.events = EPOLLET | left_status;
if (epoll_ctl(m_epfd, op, ctx->fd, &event)) {
TIGER_LOG_E(SYSTEM_LOG) << "[epoll_ctl fail"
<< " epfd:" << m_epfd
<< " op:" << op
<< " fd:" << ctx->fd
<< " errno:" << strerror(errno) << "]";
continue;
}
if (real_status & EventStatus::READ) {
ctx->trigger_event(EventStatus::READ);
--m_appending_event_cnt;
}
if (real_status & EventStatus::WRITE) {
ctx->trigger_event(EventStatus::WRITE);
--m_appending_event_cnt;
}
}
{
MutexLock::Lock lock(m_mutex);
if (is_stopping() && m_tasks.size() == 0) {
if (main_thread_id() == Thread::CurThreadId()) {
if (thread_cnt() == 0) break;
} else {
break;
}
}
}
Coroutine::Yield();
}
}