[sylar]C++高性能服务器框架——定时器模块

1,132 阅读8分钟

定时器模块概述

IOManager继承TimerManager,在idle中与epoll_wait的超时时间相结合,毫秒级的精度,在指定的超时时间结束后执行回调函数。

定时器的设计采用时间堆的方式,将所有定时器按照最小堆的方式排列,能够简单的获得当前超时时间最小的定时器,计算出超时需要等待的时间,然后等待超时。超时时间到后,获取当前的绝对时间,并且把时间堆中已经超时的所有定时器都收到一个容器中,执行他们的回调函数。

在定时器的时间计算中,都是使用绝对时间来计算的,按照绝对时间对定时器进行排序。在IOmanager中,将定时器与epoll_wait的超时时间相结合,等待时间为定时器中超时时间最小的时间。若是超时导致epoll_wait返回,那么一定是定时器设置的超时时间到了,把已经超时的定时器的任务加入到任务队列中去执行,但也有可能是别的信号使epoll_wait返回,那么仍然需要判断是否有定时器超时,所以要比较当前的绝对时间与定时器的绝对时间来判断有无定时器超时。

详解

class Timer

mumber(成员变量)

// 是否循环定时器
bool m_recurring = false;
// 执行周期
uint64_t m_ms = 0;
// 精确执行时间
uint64_t m_next = 0;
// 执行函数
std::function<void()> m_cb;
// 定时器管理器
TimerManager *m_manager = nullptr;

operator()(仿函数)

比较定时器的智能指针的大小(按执行时间排序)

bool Timer::Comparator::operator()(const Timer::ptr& lhs,
                                   const Timer::ptr& rhs) const {
    if(!lhs && !lhs) {
        return false;
    }
    if(!lhs) {
        return true;
    }
    if(!rhs) {
        return false;
    }
    if(lhs->m_next < rhs->m_next) {
        return true;
    }
    if(lhs->m_next > rhs->m_next) {
        return false;
    }
    return lhs.get() < rhs.get();
}

Timer(构造函数)

只能由TimerManager创建Timer

Timer::Timer(uint64_t ms, std::function<void()> cb,
             bool recurring, TimerManager* manager)
      :m_recurring(recurring)
      ,m_ms(ms)
      ,m_cb(cb)
      ,m_manager(manager) {
    // 执行时间为当前时间+执行周期
    m_next = sylar::GetCurrentMS() + m_ms;
}
​
Timer::Timer(uint64_t next)
    :m_next(next) {
}

cancel(取消定时器)

bool Timer::cancel() {
    TimerManager::RWMutexType::WriteLock lock(m_manager->m_mutex);
    // 
    if (m_cb) {
        m_cb = nullptr;
        // 在set中找到自身定时器
        auto it = m_manager->m_timers.find(shared_from_this());
        // 找到删除
        m_manager->m_timers.erase(it);
        return true;
    }
    return false;
}

refresh(刷新执行时间)

bool Timer::refresh() {
    TimerManager::RWMutexType::WriteLock lock(m_manager->m_mutex);
    if (!m_cb) {
        return false;
    }
    
    // 在set中找到自身定时器
    auto it = m_manager->m_timers.find(shared_from_this());
    if (it == m_manager->m_timers.end()) {
        return false;
    }
    // 删除
    m_manager->m_timers.erase(it);
    // 更新执行时间
    m_next = sylar::GetCurrentMS() + m_ms;
    // 重新插入,这样能按最新的时间排序
    m_manager->m_timers.insert(shared_from_this());
    return true;
}

reset(重置定时器时间)

from_now是否从当前时间开始计算

bool Timer::reset(uint64_t ms, bool from_now) {
    // 若周期相同,不按当前时间计算
    if (m_ms == ms && !from_now) {
        return true;
    }
    TimerManager::RWMutexType::WriteLock lock(m_manager->m_mutex);
    if (!m_cb) {
        return false;
    }
    // 在set中找到自身定时器
    auto it = m_manager->m_timers.find(shared_from_this());
    if (it == m_manager->m_timers.end()) {
        return false;
    }
    
    // 删除定时器
    m_manager->m_timers.erase(it);
    // 起始时间
    uint64_t start = 0;
    // 从现在开始计算
    if (from_now) {
        // 更新起始时间
        start = sylar::GetCurrentMS();
    } else {
        /* 起始时间为当时创建时的起始时间
         * m_next = sylar::GetCurrentMS() + m_ms; */
        start = m_next - m_ms;
    }
    // 更新数据 
    m_ms = ms;
    m_next = m_ms + start;
    // 重新加入set中
    m_manager->addTimer(shared_from_this(), lock);
    return true;
}

class TimerManager

mumber(成员变量)

// 锁
RWMutexType m_mutex;
// 定时器集合
std::set<Timer::ptr, Timer::Comparator> m_timers;
// 是否触发onTimerInsertedAtFront
bool m_tickled = false;
// 上次执行时间
uint64_t m_previouseTime = 0;

TimerManager(构造函数)

TimerManager::TimerManager() {
    m_previouseTime = sylar::GetCurrentMS();
}

addTimer(添加定时器)

public:
Timer::ptr TimerManager::addTimer(uint64_t ms, std::function<void()> cb,
                                    bool recurring) {
    // 创建定时器
    Timer::ptr timer(new Timer(ms, cb, recurring, this));
    RWMutexType::WriteLock lock(m_mutex);
    // 添加到set中
    addTimer(timer, lock);
    return timer;
}
protected:
void TimerManager::addTimer(Timer::ptr val, RWMutexType::WriteLock& lock) {
    // 添加到set中并且拿到迭代器
    auto it = m_timers.insert(val).first;
    // 如果该定时器是超时时间最短 并且 没有设置触发onTimerInsertedAtFront
    bool at_front = (it == m_timers.begin() && !m_tickled);
    // 设置触发onTimerInsertedAtFront
    if (at_front) {
        m_tickled = true;
    }
    lock.unlock();
	
    /* 触发onTimerInsertedAtFront()
	 * onTimerInsertedAtFront()在IOManager中就是做了一次tickle()的操作 */
    if (at_front) {
        onTimerInsertedAtFront();
    }
}

addConditionTimer(添加条件定时器)

weak_ptr是一种弱引用,它不会增加所指对象的引用计数,也不会阻止所指对象被销毁。

shared_ptr是一种强引用,它会增加所指对象的引用计数,直到所有shared_ptr都被销毁才会释放所指对象的内存。

在这段代码中,weak_cond是一个weak_ptr类型的对象,通过调用它的lock()方法可以得到一个shared_ptr类型的对象tmp,如果weak_ptr已经失效,则lock()方法返回一个空的shared_ptr对象。

static void OnTimer(std::weak_ptr<void> weak_cond, std::function<void()> cb) {
    // 它首先使用weak_cond的lock函数获取一个shared_ptr指针tmp
    std::shared_ptr<void> tmp = weak_cond.lock();
    // 如果tmp不为空,则调用回调函数cb。
    if (tmp) {
        cb();
    }
}

Timer::ptr TimerManager::addConditionTimer(uint64_t ms, std::function<void()> cb,
                                        std::weak_ptr<void> weak_cond,
                                        bool recurring) {
    // 在定时器触发时会调用 OnTimer 函数,并在OnTimer函数中判断条件对象是否存在,如果存在则调用回调函数cb。
    return addTimer(ms, std::bind(&OnTimer, weak_cond, cb), recurring);   
}

getNextTimer( 最近一个定时器执行的时间间隔)

uint64_t TimerManager::getNextTimer() {
    RWMutexType::ReadLock lock(m_mutex);
    // 不触发 onTimerInsertedAtFront
    m_tickled = false;
    // 如果没有定时器,返回一个最大值
    if (m_timers.empty()) {
        return ~0ull;
    }
    // 拿到第一个定时器
    const Timer::ptr& next = *m_timers.begin();
    // 现在的时间
    uint64_t now_ms = sylar::GetCurrentMS();
    // 如果当前时间 >= 该定时器的执行时间,说明该定时器已经超时了,该执行了
    if (now_ms >= next->m_next) {
        return 0;
    } else {
        // 还没超时,返回还要多久执行
        return next->m_next - now_ms;
    }
}

listExpiredCb(定时器执行列表)

void TimerManager::listExpiredCb(std::vector<std::function<void()> >& cbs) {
    // 获得当前时间
    uint64_t now_ms = sylar::GetCurrentMS();
    std::vector<Timer::ptr> expired;
    {
        RWMutexType::ReadLock lock(m_mutex);
        // 没有定时器
        if (m_timers.empty()) {
            return;
        }
    }
    RWMutexType::WriteLock lock(m_mutex);
    if (m_timers.empty()) {
        return;
    }
    // 判断服务器时间是否调后了
    bool rollover = detectClockRollover(now_ms);
    // 如果服务器时间没问题,并且第一个定时器都没有到执行时间,就说明没有任务需要执行
    if (!rollover && ((*m_timers.begin())->m_next > now_ms)) {
        return;
    }
	
    // 定义一个当前时间的定时器
    Timer::ptr now_timer(new Timer(now_ms));
    /* 若系统时间改动则将m_timers的所有Timer视为过期的
     * 否则返回第一个 >= now_ms的迭代器,在此迭代器之前的定时器全都已经超时 */
    auto it = rollover ? m_timers.end() : m_timers.lower_bound(now_timer);
    // 筛选出当前时间等于next时间执行的Timer
    while (it != m_timers.end() && (*it)->m_next == now_ms) {
        ++it;
    }
    // 小于当前时间的Timer都是已经过了时间的Timer,it是当前时间要执行的Timer
    // 将这些Timer都加入到expired中
    expired.insert(expired.begin(), m_timers.begin(), it);
    // 将已经放入expired的定时器删掉 
    m_timers.erase(m_timers.begin(), it);
    cbs.reserve(expired.size());

    // 将expired的timer放入到cbs中
    for (auto& timer : expired) {
        cbs.push_back(timer->m_cb);
        // 如果是循环定时器,则再次放入定时器集合中
        if (timer->m_recurring) {
            timer->m_next = now_ms + timer->m_ms;
            m_timers.insert(timer);
        } else {
            timer->m_cb = nullptr;
        }
    }
}

hasTimer(是否有定时器)

bool TimerManager::hasTimer() {
    RWMutexType::ReadLock lock(m_mutex);
    return m_timers.empty();
}

detectClockRollover(检测服务器时间是否被调后了)

bool TimerManager::detectClockRollover(uint64_t now_ms) {
    bool rollover = false;
    // 如果当前时间比上次执行时间还小 并且 小于一个小时的时间,相当于时间倒流了
    if (now_ms < m_previouseTime &&
        now_ms < (m_previouseTime - 60 * 60 * 1000)) {
        // 服务器时间被调过了
        rollover = true;
    }
    
    // 重新更新时间
    m_previouseTime = now_ms;
    return rollover;
}

总结

举例说明:

sylar::Timer::ptr s_timer;
void test_timer() {
    sylar::IOManager iom(2, true, "IOM2");
    s_timer = iom.addTimer(1000, []() {
        static int i = 0;
        SYLAR_LOG_INFO(g_logger) << "hello timer i = " << i;
        if (++i == 5) {
            s_timer->reset(2000, true);
        }
        if (i == 10) {
            s_timer->cancel();
        }
    }, true);
}
​
int main(int argc, char** argv) {
    g_logger->setLevel(sylar::LogLevel::INFO);
    test_timer();
    return 0;
}
2023-06-07 17:21:46     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 0
2023-06-07 17:21:47     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 1
2023-06-07 17:21:48     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 2
2023-06-07 17:21:49     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 3
2023-06-07 17:21:50     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 4
2023-06-07 17:21:52     1217    IOM2_0  6       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 5
2023-06-07 17:21:54     1217    IOM2_0  6       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 6
2023-06-07 17:21:56     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 7
2023-06-07 17:21:58     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 8
2023-06-07 17:22:00     1216    IOM2    5       [INFO]  [root]  tests/test_iomanager.cc:65      hello timer i = 9
2023-06-07 17:22:00     1216    IOM2    4       [INFO]  [system]        sylar/iomanager.cc:303  IOM2 is done
2023-06-07 17:22:02     1217    IOM2_0  3       [INFO]  [system]        sylar/iomanager.cc:303  IOM2_0 is done
  • test_timer()中定义了IO调度器,并添加了一个定时器。该定时器执行周期为1秒,当定时器被创建时,他的下一次执行时间就为当前时间 + 执行周期。
  • IOManageridle函数中通过stopping(next_timeout)中的getNextTimer()拿到当前时间与该定时器执行时间的时间差,此时间差可能已经不足1秒的时间了,epoll_Wait就等待这个时间差的时间。
  • 需要注意的是,通过定时器添加的任务都需要从idle中拿到,并在run的下一轮中去执行任务。
  • 然后通过listExpiredCb(cbs)将已经超时的定时器的回调函数全部收集到cbs中,由于该定时器是循环定时器,将该定时器重新放入到定时器集合中。然后全部schedule到任务队列中,idle交出执行权后在run的下一次循环中处理任务。
  • 处理完一次任务又陷入idle中,再次拿到该定时器的任务,循环往复。
  • 在执行到 i = 5时,通过s_timer->reset(2000, true);重置该定时器,执行周期为2秒,并且设置现在时间为起始时间。从这之后,该定时器就2秒执行一次。
  • i = 10时,取消该定时器,定时器集合为空,当idlegetNextTimer()的时间差时,返回的是一个~0ull无符号最大值,在stopping中达到停止条件,直接break了,idle结束,整个进程结束。