定时器模块
1️⃣ 什么是定时器
定时器( Timer )本质上就是一种“按时间触发任务”的机制:你把一个任务 task 交给它,并告诉它“多久以后执行”或者“每隔多久执行一次”,定时器负责在合适的时间点把这个任务触发出来。
你可以把
Timer理解成一个“闹钟”,而TimerManager就是“闹钟管理器”:你只负责设定时间和要做的事,到点后由它统一唤醒并执行对应任务。
在服务器/网络框架(比如 Sylar )里,定时器通常用来做这些事:
- 超时控制:连接超时、读写超时、
RPC超时重试 - 周期任务:心跳包、定期统计、定期清理缓存/过期会话
- 延迟执行:延迟关闭连接、延迟回收资源、延迟投递任务
- 事件循环协作:让
epoll/select等I/O等待不会“傻等”,而是能在最近的定时点醒来。具体来说,事件循环会通过getNextTimer()计算“距离最近一次定时任务还要等多久”,并把这个时间作为epoll_wait的timeout,这样既能等待I/O事件,又不会错过定时器到期触发。
2️⃣ 整体框架

3️⃣ 定时器模块
私有成员
m_ms:执行周期m_next: 精确的执行时间m_cb:定时器绑定的回调任务m_manager:定时器管理类recurring:是否是循环定时器
重要成员函数
构造函数
1
2
3
4
5
6
7
8
9Timer::Timer(bool recurring, uint64_t ms, std::function<void()> cb, TimerManager* manager)
: recurring(recurring)
, m_ms(ms)
, m_cb(cb)
, m_manager(manager)
{
m_next = sylar::GetCurrentMS() + m_ms ;
}- 作用
- 当前定时器的触发时间:
m_next = GetCurrentMS() + m_ms- 含义:从当前时间起延迟
m_ms毫秒执行回调m_cb
- 当前定时器的触发时间:
- 作用
cancel1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19bool Timer::cancel()
{
// 在多线程环境下,可能有多个线程会对 TimerManager 进行操作,所以需要加锁
TimerManager::RWMutexType::WriteLock Lock(m_manager->m_mutex);
if (m_cb) // 判断定时器是否有回调任务,没有直接返回false
{
// 如果有回调任务,将其置空
m_cb = nullptr;
// 在TimerManager中寻找Timer,将其删除
auto it = m_manager->m_timers.find(shared_from_this());
if (it != m_manager->m_timers.end())
{
// 这说明找到了,将其从中删除
m_manager->m_timers.erase(it);
}
return true;
}
return false;
}- 作用
- 这个函数主要用于取消当前定时器,让它不再被触发执行,并把它从
TimerManager的定时器集合中移除。
- 这个函数主要用于取消当前定时器,让它不再被触发执行,并把它从
- 作用
refresh1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21bool Timer::refresh()
{
TimerManager::RWMutexType::WriteLock Lock(m_manager->m_mutex);
if (!m_cb)
{
return false;
}
// 这说明定时器绑定了回调函数
auto it = m_manager->m_timers.find(shared_from_this());
if (it != m_manager->m_timers.end())
{
// 这说明定时器列表中存在该定时器,将其从中删除
m_manager->m_timers.erase(it);
// 重置执行时间
m_next = sylar::GetCurrentMS() + m_ms;
// 将该定时器重新添加到定时器列表中
m_manager->m_timers.insert(shared_from_this());
return true;
}
return false;
}- 作用
- 这个函数主要用于刷新当前定时器以及定时器绑定的时间,并把它重新插入到
TimerManager。
- 这个函数主要用于刷新当前定时器以及定时器绑定的时间,并把它重新插入到
- 作用
reset1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34bool Timer::reset(uint64_t ms, bool from_now)
{
// 这说明定时器的时间间隔相同,而且不从现在开始重新及时
if (ms == m_ms && !from_now)
{
return true;
}
// 添加锁来进行线程之间的同步
TimerManager::RWMutexType::WriteLock Lock(m_manager->m_mutex);
if (!m_cb)
{
return false;
}
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
{
start = m_next - m_ms; // 获得上次定时器的触发事件 -------> m_next : 是下次定时器的触发时间,m_ms: 是定时器到下次触发时间的时间间隔
}
m_ms = ms;
m_next = m_ms + start;
m_manager->addTimer(shared_from_this(), Lock);
return true;
}- 作用
- 如果不要求从现在重新定时且前后两次定时的时间间隔相同,就直接返回。
- 否则重新计算时间并添加到定时器集合。
- 作用
4️⃣ 定时器管理模块
私有成员
m_mutex:读写锁互斥量m_timers:定时器集合m_previousTime:上次定时器执行时间m_tickled:避免了在插入新的最早定时器时,每次都重新触发通知,而只会在第一次插入时处理它
重要成员函数
构造函数
1
2
3
4TimerManager::TimerManager()
{
m_previousTime = GetCurrentMS();
}- 作用
- 记录
TimerManager被创建时的系统运行时间(以毫秒为单位),用于后续定时器逻辑中检测系统时间是否出现回退、跳变等问题。
- 记录
- 作用
addTimer1
2
3
4
5
6
7
8
9
10
11Timer::ptr TimerManager::addTimer(uint64_t ms, bool recurring, std::function<void()> cb)
{
// 构造一个定时器
Timer::ptr timer(new Timer(recurring, ms, cb, this));
// 创建一个写者锁
TimerManager::RWMutexType::WriteLock Lock(m_mutex);
// 将定时器添加到定时器列表中
addTimer(timer,Lock);
// 返回定时器
return timer;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void TimerManager::addTimer(Timer::ptr val, RWMutexType::WriteLock& Lock)
{
auto it = m_timers.insert(val).first;
bool at_front = (it == m_timers.begin()) && !m_tickled;
if(at_front)
{
m_tickled = true;
}
Lock.unlock();
if(at_front)
{
onTimerInsertAtFront();
}
}- 作用
- 通过设置的时间间隔
ms,创建一个定时器Timer,并将其添加到定时器集合m_timers。 - 如果这个新插入的定时器位于定时器集合中的最开始位置,即这个定时器最早到期。那么就必须通知
IO线程提前醒来,比如:- 原来最早到期:还有
5000ms。 - 现在插入了一个更早到期的
timer:只剩10ms。 - 如果不唤醒
IO线程,它还会继续睡到5000ms才醒 → 这个10ms的timer就会被延迟执行。
- 原来最早到期:还有
- 为什么需要
m_tickled?m_tickled == false:说明当前还没有“唤醒请求”在路上- 第一个插到最前面的
timer负责唤醒,并把m_tickled置为true
- 第一个插到最前面的
m_tickled == true:说明已经有人触发过唤醒了(IO线程马上就会醒/正在醒)- 后续再插更早的
timer不需要重复唤醒,因为IO线程醒来后会看到集合里最新最早的那个timer。
- 后续再插更早的
- 通过设置的时间间隔
- 作用
addTimerCondition1
2
3
4Timer::ptr TimerManager::addTimerCondition(uint64_t ms, bool recurring, std::function<void()> cb, std::weak_ptr<void> weak_cond)
{
return addTimer(ms, recurring, std::bind(&onTimer,weak_cond, cb));
}onTimer1
2
3
4
5
6
7
8
9static void onTimer(std::weak_ptr<void> weak_cond, std::function<void()> cb)
{
// 这用来检测智能指针的指向的对象是否存在(通过弱智能指针weak_ptr不会使引用计数 + 1的特点)
std::shared_ptr<void> tmp = weak_cond.lock();
if (tmp)
{
cb();
}
}- 作用:
- 添加一个条件定时器——只有当某个对象(由
weak_cond指向)仍然存活时,定时器到期才会真正执行回调cb;如果对象已经析构,则回调不会执行,相当于自动失效。
- 添加一个条件定时器——只有当某个对象(由
- 作用:
listExpireCb1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63void TimerManager::listExpireCb(std::vector<std::function<void()>>& cbs)
{
// 获得系统到现在的总时间
uint64_t now = 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 = false;
// 检测是否出现回滚
if (detectClockRollover(now))
{
rollover = true;
}
// 这说明没有出现回滚,但是最近的定时器的触发时间都大于当前系统时间
if (!rollover && ((*m_timers.begin())->m_next > now))
{
return;
}
// 定义一个当前时间的定时器
Timer::ptr nowTimer(new Timer(now));
// 根据是否回滚来执行不同的操作
auto it = rollover ? m_timers.end() : m_timers.lower_bound(nowTimer);
// 找到第一个大于当前时间的定时器
while(it != m_timers.end() && (*it)->m_next == now)
{
++it;
}
// 将超时定时器全部添加到超时计时器列表中
expired.insert(expired.begin(), m_timers.begin(), it);
// 将超时定时器从原来的列表中删除
m_timers.erase(m_timers.begin(), it);
// 给回调函数集合扩容
cbs.resize(expired.size());
// 遍历超时定时器集合,将超时定时器的回调函数全部添加到回调函数集合中
for (auto& timer : expired)
{
cbs.push_back(timer->m_cb);
// 判断是不是循环定时器
if (timer->recurring)
{
timer->m_next = now + timer->m_ms;
m_timers.insert(timer);
}
else
{
timer->m_cb = nullptr;
}
}
}- 作用
- 批量取出当前已经到期的定时器回调,把它们放进
cbs供外部执行 - 把这些到期定时器从管理器中移除
- 对循环定时器重新计算下一次触发时间再插回去;另外还处理“系统时间回拨”的特殊情况。
- 批量取出当前已经到期的定时器回调,把它们放进
- 作用
参考文章:
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GYu的妙妙屋!
