在寫定時器管理器時,參考了幾篇博文:
1)
【原創】技術系列之 定時器(一)2)
libevent 源碼分析:min_heap帶來的超時機制最后決定采用<<用TCP/IP進行網際互連>>第二卷中講述TCP定時器一章中的策略, 使用一個定時器鏈表, 根據定時器觸發的時間的升序排列, 每個定時器中有一個字段存放距離鏈表中上一個定時器觸發之后的相對時間值.這樣, 每次定時器到時的時候, 都去查詢定時器鏈表的第一個節點, 如果沒有到觸發時間就直接返回;否則就依次遍歷該鏈表, 查看哪些定時器是可以觸發的, 觸發定時器事件之后將這些定時器從鏈表中取出, 然后重新插入到定時器鏈表中, 并且更新相對時間字段.這個實現想法很簡單,但是實現的時候有不少細節需要考慮,我折騰了兩天在調試!!-_-.
另外,這個定時器管理器也是一個單件.
我的這個實現還是有局限性的:
1) 每個定時器有一個ID, 定時器與ID一一對應, 在定時器中有一個字段保存當前分配的ID計數, 每新增一個定時器就加一, 但是刪除定時器卻不回收.這個字段是int型, 我想應該沒有哪個系統會加入這么多定時器以至于溢出吧.
2) 采用setitimer進行定時, 每次觸發生成ALARM信號, 這要求在使用這個定時器的時候注意處理被信號中斷的情況(比如收發網絡數據時).在參考的第一篇博文中, 采用的是生成一個新的線程, 專門處理定時操作, 使用的是select函數, 但是我認為如果這樣的話, 那么就用在代碼中注意多線程安全,這對于我這種基本不寫多線程服務器的人來說是個噩夢....
3)目前只做到精確到秒,不過,對我來說也夠用了.
timermanager.h:
/********************************************************************
created: 2008/08/10
filename: timermanager.h
author: Lichuang
purpose: 定時器管理器
*********************************************************************/
#ifndef __TIMER_MANAGER_H__
#define __TIMER_MANAGER_H__
#include "singleton.h"
#include "threadmutex.h"
#include <map>
#include <list>
#include <signal.h>
using namespace std;
enum ETimeEventType
{
TIMER_ONCE = 0, //一次型
TIMER_CIRCLE //循環型
};
struct TTimeEvent
{
int nId; // 定時器ID
int nInterval; // 定時器時間間隔(秒)
int nSecondsLeft; // 相對定時器鏈表中前一個節點觸發后的相對時間(秒)
ETimeEventType nType; // 事件類型
void (*pFn)(void* pArg); // 回調函數
void* pArg; // 回調函數參數
};
class CTimerManager
: public CSingleton<CTimerManager>
{
public:
int Init(int nInterval);
int AddTimer(TTimeEvent& tTimeEvent);
int DelTimer(int nId);
void PrintTimerList();
int Start();
int Stop();
int Process();
private:
CTimerManager();
virtual ~CTimerManager();
private:
int SetTimer(sighandler_t pFn);
int AddTimer(list<TTimeEvent>::iterator& tPos, TTimeEvent& tTimeEvent, bool bIsNewEvent);
private:
int m_nInterval;
unsigned int m_nIdCount;
list<TTimeEvent> m_lEvent;
DECLARE_SINGLETON_CLASS(CTimerManager)
};
#endif /* __TIMER_MANAGER_H__ */
timermanager.cpp:
/********************************************************************
created: 2008/08/10
filename: timermanager.cpp
author: Lichuang
purpose: 定時器管理器
*********************************************************************/
#include "timermanager.h"
#include <sys/time.h>
static void Process(int nSigNo);
CTimerManager::CTimerManager()
: m_nInterval(0)
, m_nIdCount(0)
{
}
CTimerManager::~CTimerManager()
{
}
int CTimerManager::AddTimer(TTimeEvent& tTimeEvent)
{
list<TTimeEvent>::iterator Iter1 = m_lEvent.begin();
return AddTimer(Iter1, tTimeEvent, true);
}
int CTimerManager::DelTimer(int nId)
{
list<TTimeEvent>::iterator Iter1 = m_lEvent.begin(), Iter2 = m_lEvent.end();
for (; Iter1 != Iter2; ++Iter1)
{
if (Iter1->nId == nId)
{
break;
}
}
if (Iter1 == Iter2)
{
return -1;
}
int nSecondsLeft = Iter1->nSecondsLeft;
Iter2 = Iter1;
++Iter2;
m_lEvent.erase(Iter1);
// 刪除一個定時器還要更新下一個節點(如果存在的話)的相對時間
if (m_lEvent.end() != Iter2)
{
Iter2->nSecondsLeft += nSecondsLeft;
}
return 0;
}
void CTimerManager::PrintTimerList()
{
list<TTimeEvent>::iterator Iter1 = m_lEvent.begin(), Iter2 = m_lEvent.end();
for (; Iter1 != Iter2; ++Iter1)
{
printf("id = %d, interval = %d, secondleft = %d\n", Iter1->nId, Iter1->nInterval, Iter1->nSecondsLeft);
}
}
int CTimerManager::Start()
{
return SetTimer(::Process);
}
int CTimerManager::Stop()
{
m_nInterval = 0;
return SetTimer(SIG_DFL);
}
int CTimerManager::Init(int nInterval)
{
m_nInterval = nInterval;
return 0;
}
int CTimerManager::SetTimer(sighandler_t pFn)
{
struct itimerval interval;
interval.it_interval.tv_sec = m_nInterval;
interval.it_interval.tv_usec = 0;
interval.it_value.tv_sec = m_nInterval;
interval.it_value.tv_usec = 0;
if (0 != ::setitimer(ITIMER_REAL, &interval, NULL))
{
return -1;
}
if (0 != ::signal(SIGALRM, pFn))
{
return -1;
}
return 0;
}
int CTimerManager::AddTimer(list<TTimeEvent>::iterator& tPos, TTimeEvent& tTimeEvent, bool bIsNewEvent)
{
// 根據觸發時間的升序排列, 將定時器放在鏈表的合適位置
list<TTimeEvent>::iterator Iter1 = tPos, Iter2 = m_lEvent.end();
int nSecondsLeft = 0;
for (; Iter1 != Iter2; nSecondsLeft += Iter1->nSecondsLeft, ++Iter1)
{
if (Iter1->nSecondsLeft + nSecondsLeft > tTimeEvent.nInterval)
{
break;
}
}
tTimeEvent.nSecondsLeft = tTimeEvent.nInterval - nSecondsLeft;
// 如果插入節點不是定時器鏈表的最后一個節點, 那么要修改它下一個節點的相對時間值
if (Iter1 != Iter2)
{
Iter1->nSecondsLeft = Iter1->nSecondsLeft - tTimeEvent.nSecondsLeft;
}
if (bIsNewEvent)
{
tTimeEvent.nId = m_nIdCount++;
}
m_lEvent.insert(Iter1, tTimeEvent);
return tTimeEvent.nId;
}
int CTimerManager::Process()
{
// 如果定時器鏈表是空的, 立即返回
if (m_lEvent.empty())
{
return 0;
}
// 如果定時器鏈表的頭結點沒有到該觸發的時間, 更新已經過去的時間計數器后返回
list<TTimeEvent>::iterator Iter1 = m_lEvent.begin();
Iter1->nSecondsLeft -= m_nInterval;
if (0 < Iter1->nSecondsLeft)
{
return 0;
}
// 定時器鏈表的頭結點已經可以觸發, 從頭結點開始遍歷鏈表, 看看哪些結點也可以觸發事件的
list<TTimeEvent>::iterator Iter2 = m_lEvent.end();
list<TTimeEvent> lEvent;
while (Iter1 != Iter2)
{
if (0 >= Iter1->nSecondsLeft)
{
// 觸發定時事件
Iter1->pFn(Iter1->pArg);
if (TIMER_CIRCLE == Iter1->nType)
{
lEvent.push_back(*Iter1);
}
++Iter1;
m_lEvent.pop_front();
}
else
{
break;
}
}
// 將本次觸發的定時器事件重新插入到定時器鏈表中
// 這里有一個優化策略: nLastInterval 保存上一個重新插入到定時器鏈表中的時間間隔
// 如果這個值比當前節點的時間間隔小, 那么從上一個插入的位置開始搜索
// 否則從定時器節點的開始位置搜索
int nLastInterval = 0;
list<TTimeEvent>::iterator Iter3;
for (Iter1 = lEvent.begin(), Iter2 = lEvent.end(); Iter1 != Iter2; nLastInterval = Iter1->nInterval, ++Iter1)
{
if (0 == nLastInterval || nLastInterval > Iter1->nInterval)
{
Iter3 = m_lEvent.begin();
}
AddTimer(Iter3, *Iter1, false);
}
return 0;
}
void Process(int nSigNo)
{
if (nSigNo == SIGALRM)
{
CTimerManager::GetInstance()->Process();
}
}