一、定時器
1、定時器是什么?
定時器不僅存在于硬件領域,在軟件層面(客戶端、網頁和服務端)也普遍應用,核心功能都是高效管理大量延時任務。不同應用場景下,其實現方式和使用方法有所差異。
2、定時器解決了什么問題?
可以定期清理緩存,定時備份數據,在服務器負載較大時,自動執行一些重要的功能,提高服務器的效率和穩定性。
3、是怎么解決的?
- 組織大量延時任務的數據結構(容器)
- 觸發最近將超時的任務的機制
4、實現方式:
- 對任務按觸發時間進行排序:紅黑樹(map,set,multimap,multiset)–nginx,最小堆–libevent,libev,go,應用在單線程場景下
- 1、觸發時刻作為key,任務作為val
- 2、快速找到最近要超時的任務
- 3、觸發后要刪除該任務且支持隨時刪除任務
- 4、允許相同時刻觸發任務
- 對執行順序進行組織:時間輪,針對當前時間指針做偏移。–netty,skynet,kafka,應用在多線程場景下
5、有哪些常用觸發機制?
- I/O多路復用的最后一個超時參數
- 將定時器轉化為io處理,timerfd
6、使用場景
- 與網絡模塊協同處理
- 基于事件驅動業務開展
- 除了協同網絡處理,復用系統調用
二、具體實現
1、采用紅黑樹,對任務按觸發時間進行排序
- map<key, value>: 以key存觸發時間,value存任務,那么可能存在多個同一時刻的任務,不選
- multimap<key, value>:可能存儲重復的值,然后操作起來比較麻煩,不選
- set: 不可能出現重復的,可以
那么以一個自定義結構作為key值進行存儲,并且按觸發時間進行排序
typedef struct TimerNode_S{time_t expire;//過期時間uint64_t id; //由于可能存在多個定時器在同一時間過期,所以需要一個唯一標識}TimerNode_S;bool operator < (const TimerNode_S& lhd, const TimerNode_S& rhd)
{if(lhd.expire < rhd.expire){return true;}else if(lhd.expire > rhd.expire){return false;}else{ //如果相等,誰先插入,誰就先執行return lhd.id < rhd.id;}
}set<TimerNode, less<>> timeouts;
2、計算最近觸發的定時任務離當前還有多久?
time_t TimeOut()
{auto iter = timeouts.begin();if (iter == timeouts.end()) {return -1;}time_t t = iter->expire - GetTick();return t > 0 ? t : 0;
}
3、獲取當前時間
/**
* @brief 獲取當前時間的時間戳(以毫秒為單位)
*
* 使用 std::chrono::steady_clock 獲取從系統啟動到當前的時間
* std::chrono::system_clock,受系統時間影響,可能會被修改
*
* @return 返回當前時間的時間戳(以毫秒為單位)
*/
static inline time_t GetTick()
{return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
}
4、添加定時器
/**
* @brief 添加定時器
*
* 將一個定時器節點添加到定時器列表中,并返回該節點的標識。
*
* @param msec 定時器超時時間,單位為毫秒
* @param cb 定時器超時時執行的回調函數
*
* @return 返回定時器的標識,類型為 TimerNode_S
*/
TimerNode_S AddTimer(int msec, TimerNode::Callback cb)
{time_t expire = GetTick() + msec; //過期時間if(timeouts.empty() || expire <= timeouts.crbegin()->expire){auto pairs = timeouts.emplace(GetID(), expire, move(cb));return static_cast<TimerNode_S>(*pairs.first);}auto ele = timeouts.emplace_hint(timeouts.crbegin().base(), GetID(), expire, move(cb));return static_cast<TimerNode_S>(*ele);
}
5、刪除定時器
/**
* @brief 刪除定時器節點
*
* 從定時器集合中刪除指定的定時器節點。
*
* @param node 需要刪除的定時器節點
*/
void DelTimer(TimerNode_S& node)
{auto iter = timeouts.find(node);if(iter != timeouts.end()){timeouts.erase(iter);}
}
6、處理定時器任務
/**
* @brief 處理超時事件
*
* 該函數遍歷超時事件列表,對于已超時的每個事件,調用其回調函數進行處理,并從列表中移除該事件。
*
* @param now 當前時間戳
*/
void HandleTimeout(time_t now)
{auto iter = timeouts.begin();while(iter != timeouts.end() && iter->expire <= now){iter->cb(*iter);iter = timeouts.erase(iter);}
}struct epoll_event evs[64] = {0};
while(true){int n = epoll_wait(epfd, evs, 64, timer->TimeOut());time_t now = CTimer::GetTick();for(int j = 0; j < n; ++j){cout<<"epoll_wait:"<<endl;}timer->HandleTimeout(now);
}
以上是采用I/O多路復用的最后一個超時參數,接下來更換成timerfd
7、主要調用的函數
/*
*功能:創建定時器
*clockfd: CLOCK_REALTIME-系統實時時鐘,與系統時間同步,受用戶手動修改時間影響。CLOCK_MONOTONIC-單調遞增時鐘,自系統啟動以來的時間,不受系統時間調整的影響
*flags:TFD_NONBLOCK(非阻塞模式)和 TFD_CLOEXEC(在 exec 調用時自動關閉文件描述符)
*
*/
timerfd_create(int clockfd, int flags);/*
* 功能:用于設置定時器的初始超時時間和后續周期時間
* fd:timerfd
* flags: 控制定時器行為的標志:
* 0-------絕對時間
* TFD_TIMER_ABSTIME------表示相對時間
* new_value: 指定了定時器的初始超時時間和(可選的)后續周期時間
* old_value: 用于恢復定時器到之前的狀態,常設為nullptr
*/
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
8、將之前的計算觸發時間的方式改成timerfd
void UpdateTimerfd(const int fd) {struct timespec abstime;auto iter = timeouts.begin();if (iter != timeouts.end()) {abstime.tv_sec = iter->expire / 1000;abstime.tv_nsec = (iter->expire % 1000) * 1000000;} else {abstime.tv_sec = 0;abstime.tv_nsec = 0;}struct itimerspec its = {.it_interval = {},.it_value = abstime};timerfd_settime(fd, TFD_TIMER_ABSTIME, &its, nullptr);
}int tfd = timerfd_create(CLOCK_MONOTONIC, 0);struct epoll_event evs[64] = {0};
while(true){timer->UpdateTimerfd(tfd);int n = epoll_wait(epfd, evs, 64, -1);time_t now = CTimer::GetTick();for(int j = 0; j < n; ++j){cout<<"epoll_wait:"<<endl;}timer->HandleTimeout(now);
}
三、總結
- 定時器在程序中無處不在,無論是硬件,還是網頁,客戶端,服務端等。
- 在服務端上合理地使用定時器,能提高服務器的效率和穩定性,如定時清理緩存,在服務器高負載情況下,自動執行一些重要的任務等。
- 定時器的數據結構多種多樣,有根據觸發時間排序的紅黑樹,最小堆,也有根據執行順序的時間輪。
- 服務端常與網絡模塊協同處理
- 服務端常基于事件驅動業務開展
- 服務端除了協同網絡處理,復用系統調用
代碼:
Code
0voice·Github