高性能分布式网络服务器--定时器模块

定时器模块

服务器程序通常需要处理众多定时事件,如何有效地组织与管理这些定时事件对服务器的性能至关重要。为此,我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构,比如链表、排序链表和时间轮,将所有定时器串联起来,以实现对定时事件的统一管理

每个定时器通常至少包含一个超时时间和一个任务回调函数。此外,定时器还可以包括回调函数参数及是否自动重启等信息

通常有两种高效管理定时器的容器:时间轮和时间堆

github

https://github.com/huxiaohei/tiger.git

实现

tiger中的定时器采用最小堆设计,所有定时器根据绝对的超时时间点进行排序,每次取出离当前时间最近的一个超时时间点,计算出超时需要等待的时间,然后等待超时。超时时间到后,获取当前的绝对时间点,然后把最小堆里超时时间点小于这个时间点的定时器都收集起来,执行它们的回调函数

定时器虽然采用绝对事件排序,但是为了开发同学使用方便,一般接口都是提供相对时间,比如相对当前时间3秒后执行。会根据传入的相对时间和当前的绝对时间计算出定时器超时时的绝对时间点

tiger定时器只支持毫秒级,因为定时器的超时等待是基于epoll_wait实现,epoll_wait的超时精度也只有毫秒级

TimerManager的设计

首先,我们在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<Timer::ptr, Timer::Comparator>。因此,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的超时时间

为此,添加一个Timerm_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();
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎小黑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值