在程序开发过程中,定时器会经常被使用到。而在Linux应用开发中,系统定时器资源有限,进程可创建的定时器数量会受到系统限制。假如随便滥用定时器,会导致定时器资源不足,其他模块便无法申请到定时器资源。 如上,假如同一进程中多个模块,需要同时申请不同周期定时器,就会导致模块创建定时器失败。
为解决定时器资源紧缺的问题,通常有以下几种方案:
两者相比,各有优劣。最小堆方式精度更高,时间轮方式则胜在效率。在定时任务数量不庞大的情况下,最小堆方式更合适。本篇主要介绍最小堆的实现。
通过对定时器功能的理解,可以将其抽象为三个类:系统定时器,定时器任务,定时器任务管理。其类图如下:
定时器管理组件
编程环境
接口定义
class SprSystemTimer : public SprObserver
{
public:
SprSystemTimer(ModuleIDType id, const std::string& name, std::shared_ptr<SprMediatorProxy> mediatorPtr);
~SprSystemTimer();
SprSystemTimer(const SprSystemTimer&) = delete;
SprSystemTimer& operator=(const SprSystemTimer&) = delete;
SprSystemTimer(SprSystemTimer&&) = delete;
SprSystemTimer& operator=(SprSystemTimer&&) = delete;
int ProcessMsg(const SprMsg& msg);
int Init();
int InitTimer();
int StartTimer(uint32_t intervalInMilliSec);
int StopTimer();
int DestoryTimer();
private:
bool mTimerRunning;
int mTimerFd;
};
class SprTimer
{
public:
SprTimer(uint32_t moduleId, uint32_t msgId, uint32_t repeatTimes, uint32_t delayInMilliSec, uint32_t intervalInMilliSec);
SprTimer(const SprTimer& timer);
~SprTimer();
bool operator < (const SprTimer& t) const;
bool IsExpired() const;
uint32_t GetTick() const;
uint32_t GetModuleId() const { return mModuleId; }
uint32_t GetMsgId() const { return mMsgId; }
uint32_t GetIntervalInMilliSec() const { return mIntervalInMilliSec; }
uint32_t GetExpired() const { return mExpired; }
uint32_t GetRepeatTimes() const { return mRepeatTimes; }
uint32_t GetRepeatCount() const { return mRepeatCount; }
void SetExpired(uint32_t expired) { mExpired = expired; }
void RepeatCount() const { mRepeatCount++; }
private:
uint32_t mModuleId;
uint32_t mMsgId;
uint32_t mIntervalInMilliSec;
uint32_t mExpired;
uint32_t mRepeatTimes;
mutable uint32_t mRepeatCount;
};
class SprTimerManager : public SprObserver
{
public:
virtual ~SprTimerManager();
int Init();
static SprTimerManager* GetInstance(ModuleIDType id, const std::string& name, std::shared_ptr<SprMediatorProxy> mediatorPtr, std::shared_ptr<SprSystemTimer> systemTimerPtr);
private:
SprTimerManager(ModuleIDType id, const std::string& name, std::shared_ptr<SprMediatorProxy> mediatorPtr, std::shared_ptr<SprSystemTimer> systemTimerPtr);
int DeInit();
int InitSystemTimer();
int ProcessMsg(const SprMsg& msg) override;
int PrintRealTime();
// --------------------------------------------------------------------------------------------
// - Module's timer book manager functions
// --------------------------------------------------------------------------------------------
int AddTimer(uint32_t moduleId, uint32_t msgId, uint32_t repeatTimes, int32_t delayInMilliSec, int32_t intervalInMilliSec);
int AddTimer(const SprTimer& timer);
int DelTimer(const SprTimer& timer);
int UpdateTimer();
int CheckTimer();
uint32_t NextExpireTimes();
// --------------------------------------------------------------------------------------------
// - Message handle functions
// --------------------------------------------------------------------------------------------
void MsgRespondStartSystemTimer(const SprMsg &msg);
void MsgRespondStopSystemTimer(const SprMsg &msg);
void MsgRespondAddTimer(const SprMsg &msg);
void MsgRespondDelTimer(const SprMsg &msg);
void MsgRespondSystemTimerNotify(const SprMsg &msg);
void MsgRespondClearTimersForExitComponent(const SprMsg &msg);
private:
bool mEnable; // Component init status
std::set<SprTimer> mTimers; // sort by SprTimer.mExpired from smallest to largest
std::shared_ptr<SprSystemTimer> mSystemTimerPtr; // SysTimer object
};
TimerManager
中存储定时任务的容器用的std::set<Timer>
,可以自定义按照时间戳从小到大排序,就不用自己实现二叉堆结构了。
如下是TimerManager
中定时器触发的业务逻辑代码:
① 定时器触发后,从头遍历任务容器。
② 若当前任务已超时且任务未失效,通知定时器触发事件。将当前任务缓存至失效容器
,若为重复定时器,更新时间戳,再次插入任务容器。
③ 若当前任务未到期(说明后续任务都未到期),退出容器遍历。与②互斥。
④ 从任务容器中,删除②中缓存的失效容器
。
⑤ 当前任务容器若为空,停止系统定时器。
void SprTimerManager::MsgRespondSystemTimerNotify(const SprMsg &msg)
{
set<SprTimer> deleteTimers;
// loop: Execute the triggered timers, timers are sorted by Expired value from smallest to largest
for (auto it = mTimers.begin(); it != mTimers.end(); ++it) {
if (it->IsExpired()) {
if (it->GetRepeatTimes() == 0 || (it->GetRepeatCount() + 1) < it->GetRepeatTimes()) {
SprTimer t(*it);
// loop: update timer valid expired time
uint32_t tmpExpired = t.GetExpired();
do {
tmpExpired += t.GetIntervalInMilliSec();
t.RepeatCount();
} while (tmpExpired < it->GetTick());
if (it->GetRepeatTimes() == 0 || (it->GetRepeatCount() + 1) < it->GetRepeatTimes()) {
t.SetExpired(tmpExpired);
AddTimer(t);
}
}
// Notify expired timer event to the book component
SprMsg msg(it->GetModuleId(), it->GetMsgId());
NotifyObserver(msg);
it->RepeatCount();
deleteTimers.insert(*it);
} else {
break;
}
}
// Delete expired timers
for (const auto& timer : deleteTimers) {
DelTimer(timer);
}
// Set next system timer
uint32_t msgId = mTimers.empty() ? SIG_ID_TIMER_STOP_SYSTEM_TIMER : SIG_ID_TIMER_START_SYSTEM_TIMER;
SprMsg sysMsg(msgId);
SendMsg(sysMsg);
// SPR_LOGD("Current total timers size = %d\n", (int)mTimers.size());
}
测试一个2s的定时器:
56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:16.586
56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:18.586
56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:20.586
56 DebugCore D: msg id: SIG_ID_DEBUG_TIMER_TEST_2S 2024-03-03 19:26:22.585
STL
接口的std::set<Timer>
容器,通过重载Timer
运算符<
,实现按照时间戳(mExpired)从小到大排序。用心感悟,认真记录,写好每一篇文章,分享每一框干货。