在后端的開發(fā)中,定時器有很廣泛的應(yīng)用。
比如:
心跳檢測
倒計時
游戲開發(fā)的技能冷卻
redis的鍵值的有效期等等,都會使用到定時器。
定時器的實現(xiàn)數(shù)據(jù)結(jié)構(gòu)選擇
紅黑樹
對于增刪查,時間復(fù)雜度為O(logn),對于紅黑樹最?節(jié)點為最左側(cè)節(jié)點,時間復(fù)雜度O(logn)
最小堆
對于增查,時間復(fù)雜度為O(logn),對于刪時間復(fù)雜度為O(n),但是可以通過輔助數(shù)據(jù)結(jié)構(gòu)( map 或者hashtable來快速索引節(jié)點)來加快刪除操作;對于最?節(jié)點為根節(jié)點,時間復(fù)雜度為O(1)
跳表
對于增刪查,時間復(fù)雜度為O(logn),對于跳表最?節(jié)點為最左側(cè)節(jié)點,時間復(fù)雜度為O(1),但是空間復(fù)雜度?較?,為O(1.5n)
時間輪
對于增刪查,時間復(fù)雜度為O(1),查找最?節(jié)點也為O(1)
libco的使用了時間輪的實現(xiàn)
首先,時間輪有幾個結(jié)構(gòu),必須理清他們的關(guān)系。
struct stTimeoutItem_t
{
enum { eMaxTimeout = 40 * 1000 }; // 40s
stTimeoutItem_t* pPrev; // 前
stTimeoutItem_t* pNext; // 后
stTimeoutItemLink_t* pLink; // 鏈表,沒有用到,寫這里有毛用
OnPreparePfn_t pfnPrepare; // 不是超時的事件的處理函數(shù)
OnProcessPfn_t pfnProcess; // resume協(xié)程回調(diào)函數(shù)
void* pArg; // routine 協(xié)程對象指針
bool bTimeout; // 是否超時
unsigned long long ullExpireTime; // 到期時間
};
struct stPoll_t;
struct stPollItem_t : public stTimeoutItem_t
{
struct pollfd* pSelf; // 對應(yīng)的poll結(jié)構(gòu)
stPoll_t* pPoll; // 所屬的stPoll_t
struct epoll_event stEvent; // epoll事件,poll轉(zhuǎn)換過來的
};
// co_poll_inner 創(chuàng)建,管理這多個stPollItem_t
struct stPoll_t : public stTimeoutItem_t
{
struct pollfd* fds; // poll 的fd集合
nfds_t nfds; // poll 事件個數(shù)
stPollItem_t* pPollItems; // 要加入epoll 事件
int iAllEventDetach; // 如果處理過該對象的子項目pPollItems,賦值為1
int iEpollFd; // epoll fd句柄
int iRaiseCnt; // 此次觸發(fā)的事件數(shù)
};
我把這幾個結(jié)構(gòu)拉一起了,
其中,能看出,stCoEpool_t管理了這一切
// TimeoutItem的鏈表
struct stTimeoutItemLink_t
{
stTimeoutItem_t* head;
stTimeoutItem_t* tail;
};
// TimeOut
struct stTimeout_t // 時間倫
{
stTimeoutItemLink_t* pItems; // 時間輪鏈表,開始初始化分配只一圈的長度,后續(xù)直接使用
int iItemSize; // 超時鏈表中一圈的tick 60*1000
unsigned long long ullStart; // 時間輪開始時間,會一直變化
long long llStartIdx; // 時間輪開始的下標(biāo),會一直變化
};
// epoll 結(jié)構(gòu)
struct stCoEpoll_t
{
int iEpollFd;
static const int _EPOLL_SIZE = 1024 * 10;
struct stTimeout_t* pTimeout; // epoll 存著時間輪
struct stTimeoutItemLink_t* pstTimeoutList; // 超時事件鏈表
struct stTimeoutItemLink_t* pstActiveList; // 用于signal時會插入
co_epoll_res* result;
};
也就是說,一個協(xié)程,就有一個,在co_init_curr_thread_env 中創(chuàng)建
它管理著超時鏈表,信號事件鏈表
其中的pTimeout,就是時間輪,也就是一個數(shù)組,這個數(shù)組的大小位60*1000
stTimeout_t *AllocTimeout( int iSize )
{
stTimeout_t *lp = (stTimeout_t*)calloc( 1,sizeof(stTimeout_t) );
lp- >iItemSize = iSize;
// 注意這里先把item分配好了,后續(xù)直接使用
lp- >pItems = (stTimeoutItemLink_t*)calloc( 1, sizeof(stTimeoutItemLink_t) * lp- >iItemSize );
lp- >ullStart = GetTickMS();
lp- >llStartIdx = 0;
return lp;
}
這就是分配的時間輪的方法,首先指定了下標(biāo)時間等信息,根據(jù)結(jié)構(gòu)注釋應(yīng)該不難懂
// apTimeout:時間輪
// apItem: 某一個定時item
// allNow:當(dāng)前的時間
// 函數(shù)目的,將超時項apItem加入到apTimeout
int AddTimeout( stTimeout_t *apTimeout, stTimeoutItem_t *apItem ,unsigned long long allNow )
{
// 這個判斷有點多余,start正常已經(jīng)分配了
if( apTimeout->ullStart == 0 )
{
apTimeout->ullStart = allNow;
apTimeout->llStartIdx = 0;
}
// 當(dāng)前時間也不大可能比前面的時間大
if( allNow < apTimeout->ullStart )
{
co_log_err("CO_ERR: AddTimeout line %d allNow %llu apTimeout->ullStart %llu",
LINE ,allNow,apTimeout->ullStart);
return __LINE__;
}
if( apItem- >ullExpireTime < allNow )
{
co_log_err("CO_ERR: AddTimeout line %d apItem- >ullExpireTime %llu allNow %llu apTimeout- >ullStart %llu",
__LINE__,apItem- >ullExpireTime,allNow,apTimeout- >ullStart);
return __LINE__;
}
// 到期時間到start的時間差
unsigned long long diff = apItem- >ullExpireTime - apTimeout- >ullStart;
// itemsize 實際上是毫秒數(shù),如果超出了,說明設(shè)置的超時時間過長
if( diff >= (unsigned long long)apTimeout- >iItemSize )
{
diff = apTimeout- >iItemSize - 1;
co_log_err("CO_ERR: AddTimeout line %d diff %d",
__LINE__,diff);
//return __LINE__;
}
// 將apItem加到末尾
AddTail( apTimeout- >pItems + ( apTimeout- >llStartIdx + diff ) % apTimeout- >iItemSize , apItem );
return 0;
}
其實,這里有個概念,stTimeoutItemLink_t 與stTimeoutItem_t,也就是說,stTimeout_t里面管理的時60*1000個鏈表,而每個鏈表有一個或者多個stTimeoutItem_t,下面這個函數(shù),就是把節(jié)點Item加入到鏈表的方法。
template
void inline AddTail(TLink*apLink, TNode ap)
{
if( ap->pLink )
{
return ;
}
if(apLink->tail)
{
apLink->tail->pNext = (TNode )ap;
ap->pNext = NULL;
ap->pPrev = apLink->tail;
apLink->tail = ap;
}
else
{
apLink->head = apLink->tail = ap;
ap->pNext = ap->pPrev = NULL;
}
ap->pLink = apLink;
}

到這里,基本把一個超時事件添加到時間輪中了,這時就應(yīng)該切換協(xié)程了co_yield_env
int ret = AddTimeout( ctx->pTimeout, &arg, now );
int iRaiseCnt = 0;
if( ret != 0 )
{
co_log_err("CO_ERR: AddTimeout ret %d now %lld timeout %d arg.ullExpireTime %lld",
ret,now,timeout,arg.ullExpireTime);
errno = EINVAL;
iRaiseCnt = -1;
}
else
{
co_yield_env( co_get_curr_thread_env() );
iRaiseCnt = arg.iRaiseCnt;
}
接下來,看怎么檢測超時事件co_eventloop
for(;;)
{
// 等待事件或超時1ms
int ret = co_epoll_wait( ctx->iEpollFd,result,stCoEpoll_t::_EPOLL_SIZE, 1 );
// 遍歷所有ret事件處理
for(int i=0;i< ret;i++)
{
pfnPrepare(xxx)
}
// 取出所有的超時時間item,設(shè)置為超時
TakeAllTimeout( ctx- >pTimeout, now, plsTimeout );
stTimeoutItem_t *lp = plsTimeout- >head;
while( lp )
{
lp- >bTimeout = true;
lp = lp- >pNext;
}
// 將超時鏈表plsTimeout加入到plsActive
Join< stTimeoutItem_t, stTimeoutItemLink_t >( plsActive, plsTimeout );
lp = plsActive- >head;
while( lp )
{
// 彈出鏈表頭,處理超時事件
PopHead< stTimeoutItem_t,stTimeoutItemLink_t >( plsActive );
if (lp- >bTimeout && now < lp- >ullExpireTime)
{
int ret = AddTimeout(ctx- >pTimeout, lp, now);
if (!ret)
{
lp- >bTimeout = false;
lp = plsActive- >head;
continue;
}
}
// 只有stPool_t 才需要切協(xié)程,要切回去了
if( lp- >pfnProcess )
{
lp- >pfnProcess( lp );
}
lp = plsActive- >head;
}
// 如果傳入該函數(shù)指針,則可以控制event_loop 退出
if( pfn )
{
if( -1 == pfn( arg ) )
{
break;
}
}
}
其中包括了定時事件處理,協(xié)程切換,主協(xié)程退出等操作。如果設(shè)置了主協(xié)程退出函數(shù),則主協(xié)程可以正常的退出。
-
定時器
+關(guān)注
關(guān)注
23文章
3274瀏覽量
116882 -
數(shù)據(jù)結(jié)構(gòu)
+關(guān)注
關(guān)注
3文章
573瀏覽量
40540 -
數(shù)組
+關(guān)注
關(guān)注
1文章
419瀏覽量
26324 -
Redis
+關(guān)注
關(guān)注
0文章
382瀏覽量
11266
發(fā)布評論請先 登錄
軟件定時器的特點和原理
什么是軟件定時器?基于STM32的軟件定時器該怎樣去實現(xiàn)呢
GPIB命令的數(shù)據(jù)結(jié)構(gòu)
GPIB命令的數(shù)據(jù)結(jié)構(gòu)
定時器的結(jié)構(gòu)及工作模式

數(shù)據(jù)結(jié)構(gòu)是什么_數(shù)據(jù)結(jié)構(gòu)有什么用

降低了CPU負載率的μC/OS-II定時器有效改進方法

什么是數(shù)據(jù)結(jié)構(gòu)?為什么要學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)?數(shù)據(jù)結(jié)構(gòu)的應(yīng)用實例分析

STM32基于cubeMX實現(xiàn)定時器點燈

定時器作用及實現(xiàn)定時器數(shù)據(jù)結(jié)構(gòu)選取介紹1

定時器作用及實現(xiàn)定時器數(shù)據(jù)結(jié)構(gòu)選取介紹2

評論