精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

定時器的實現數據結構選擇

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-13 14:22 ? 次閱讀

在后端的開發中,定時器有很廣泛的應用。

比如:

心跳檢測

倒計時

游戲開發的技能冷卻

redis的鍵值的有效期等等,都會使用到定時器。

定時器的實現數據結構選擇

紅黑樹

對于增刪查,時間復雜度為O(logn),對于紅黑樹最?節點為最左側節點,時間復雜度O(logn)

最小堆

對于增查,時間復雜度為O(logn),對于刪時間復雜度為O(n),但是可以通過輔助數據結構( map 或者hashtable來快速索引節點)來加快刪除操作;對于最?節點為根節點,時間復雜度為O(1)

跳表

對于增刪查,時間復雜度為O(logn),對于跳表最?節點為最左側節點,時間復雜度為O(1),但是空間復雜度?較?,為O(1.5n)

時間輪

對于增刪查,時間復雜度為O(1),查找最?節點也為O(1)

libco的使用了時間輪的實現

首先,時間輪有幾個結構,必須理清他們的關系。

struct stTimeoutItem_t
{
	enum { eMaxTimeout = 40 * 1000 };	// 40s
	stTimeoutItem_t* pPrev;				// 前
	stTimeoutItem_t* pNext;				// 后
	stTimeoutItemLink_t* pLink;			// 鏈表,沒有用到,寫這里有毛用

	OnPreparePfn_t pfnPrepare;			// 不是超時的事件的處理函數
	OnProcessPfn_t pfnProcess;			// resume協程回調函數

	void* pArg;							// routine 協程對象指針
	bool bTimeout;						// 是否超時
	unsigned long long ullExpireTime;	// 到期時間
};

struct stPoll_t;
struct stPollItem_t : public stTimeoutItem_t
{
	struct pollfd* pSelf;			// 對應的poll結構
	stPoll_t* pPoll;				// 所屬的stPoll_t
	struct epoll_event stEvent;		// epoll事件,poll轉換過來的
};

// co_poll_inner 創建,管理這多個stPollItem_t
struct stPoll_t : public stTimeoutItem_t
{
	struct pollfd* fds;				// poll 的fd集合
	nfds_t nfds;					// poll 事件個數
	stPollItem_t* pPollItems;		// 要加入epoll 事件
	int iAllEventDetach;			// 如果處理過該對象的子項目pPollItems,賦值為1
	int iEpollFd;					// epoll fd句柄
	int iRaiseCnt;					// 此次觸發的事件數
};

我把這幾個結構拉一起了,

圖片

其中,能看出,stCoEpool_t管理了這一切

// TimeoutItem的鏈表
struct stTimeoutItemLink_t
{
	stTimeoutItem_t* head;
	stTimeoutItem_t* tail;
};

// TimeOut 
struct stTimeout_t	// 時間倫
{
	stTimeoutItemLink_t* pItems;	// 時間輪鏈表,開始初始化分配只一圈的長度,后續直接使用
	int iItemSize;					// 超時鏈表中一圈的tick 60*1000
	unsigned long long ullStart;	// 時間輪開始時間,會一直變化
	long long llStartIdx;			// 時間輪開始的下標,會一直變化
};

// epoll 結構
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;
};

也就是說,一個協程,就有一個,在co_init_curr_thread_env 中創建

它管理著超時鏈表,信號事件鏈表

其中的pTimeout,就是時間輪,也就是一個數組,這個數組的大小位60*1000

圖片

stTimeout_t *AllocTimeout( int iSize )
{
	stTimeout_t *lp = (stTimeout_t*)calloc( 1,sizeof(stTimeout_t) );
	lp- >iItemSize = iSize;
	// 注意這里先把item分配好了,后續直接使用
	lp- >pItems = (stTimeoutItemLink_t*)calloc( 1, sizeof(stTimeoutItemLink_t) * lp- >iItemSize );
	lp- >ullStart = GetTickMS();
	lp- >llStartIdx = 0;
	return lp;
}

這就是分配的時間輪的方法,首先指定了下標時間等信息,根據結構注釋應該不難懂

// apTimeout:時間輪
// apItem: 某一個定時item
// allNow:當前的時間
// 函數目的,將超時項apItem加入到apTimeout
int AddTimeout( stTimeout_t *apTimeout, stTimeoutItem_t *apItem ,unsigned long long allNow )
{
// 這個判斷有點多余,start正常已經分配了
if( apTimeout->ullStart == 0 )
{
apTimeout->ullStart = allNow;
apTimeout->llStartIdx = 0;
}
// 當前時間也不大可能比前面的時間大
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 實際上是毫秒數,如果超出了,說明設置的超時時間過長
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_tstTimeoutItem_t,也就是說,stTimeout_t里面管理的時60*1000個鏈表,而每個鏈表有一個或者多個stTimeoutItem_t,下面這個函數,就是把節點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;
}

![圖片](//file1.elecfans.com/web2/M00/AD/ED/wKgaomVRwIaAdU0AAADdUXMLTLQ483.jpg)

到這里,基本把一個超時事件添加到時間輪中了,這時就應該切換協程了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,設置為超時
	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 才需要切協程,要切回去了
		if( lp- >pfnProcess )
		{
			lp- >pfnProcess( lp );
		}
		lp = plsActive- >head;
	}

	// 如果傳入該函數指針,則可以控制event_loop 退出
	if( pfn )
	{
		if( -1 == pfn( arg ) )
		{
			break;
		}
	}
}
其中包括了定時事件處理,協程切換,主協程退出等操作。如果設置了主協程退出函數,則主協程可以正常的退出。
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 定時器
    +關注

    關注

    23

    文章

    3218

    瀏覽量

    113668
  • 數據結構
    +關注

    關注

    3

    文章

    568

    瀏覽量

    40030
  • 數組
    +關注

    關注

    1

    文章

    411

    瀏覽量

    25821
  • Redis
    +關注

    關注

    0

    文章

    368

    瀏覽量

    10780
收藏 人收藏

    評論

    相關推薦

    軟件定時器的特點和原理

    本文介紹了軟件定時器的特點和原理,并從時鐘節拍,數據結構,定時器操作等角度分析,實現了基于STM32的軟件定時器,該軟件
    發表于 08-19 08:29

    什么是軟件定時器?基于STM32的軟件定時器該怎樣去實現

    目錄1.什么是軟件定時器2.軟件定時器實現原理3.基于STM32的軟件定時器3.1 時鐘節拍3.2 數據結構3.3
    發表于 12-22 07:47

    GPIB命令的數據結構

    針對GPIB命令的結構,提出一種存儲GPIB命令的數據結構。根據GPIB命令的層次關系的特點,選擇數據結構中“樹”的概念來存儲GPIB命令結點;并考慮程序
    發表于 02-10 16:20 ?70次下載

    GPIB命令的數據結構

    針對GPIB命令的結構,提出一種存儲GPIB命令的數據結構。根據GPIB命令的層次關系的特點,選擇數據結構中“樹”的概念來存儲GPIB命令結點;并考慮程序
    發表于 01-04 10:13 ?0次下載

    定時器結構及工作模式

    定時器是單片機的重要功能模塊之一,在檢測、控制領域有廣泛應用。 定時器常用作定時時鐘,以實現定時檢測、
    發表于 09-25 10:08 ?4次下載
    <b class='flag-5'>定時器</b>的<b class='flag-5'>結構</b>及工作模式

    數據結構是什么_數據結構有什么用

    數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關系的數據元素的集合。通常情況下,精心選擇
    發表于 11-17 14:45 ?1.6w次閱讀
    <b class='flag-5'>數據結構</b>是什么_<b class='flag-5'>數據結構</b>有什么用

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

    對μC/OS-II的定時器管理算法進行改進的主要目標是:要么不對定時器進行檢查,要檢查則一定有定時器到期[2]。為了達到這個設計目標,需要對μC/OS-II的定時器輪進行重新設計。采用
    發表于 07-19 07:06 ?871次閱讀
    降低了CPU負載率的μC/OS-II<b class='flag-5'>定時器</b>有效改進方法

    什么是數據結構?為什么要學習數據結構?數據結構的應用實例分析

    本文檔的主要內容詳細介紹的是什么是數據結構?為什么要學習數據結構?數據結構的應用實例分析包括了:數據結構在串口通信當中的應用,數據結構在按鍵
    發表于 09-26 15:45 ?14次下載
    什么是<b class='flag-5'>數據結構</b>?為什么要學習<b class='flag-5'>數據結構</b>?<b class='flag-5'>數據結構</b>的應用實例分析

    STM32基于cubeMX實現定時器點燈

    Cortex M3內核當中的定時器,它并不屬于芯片廠商的外設,也就是說使用ARM內核的不同廠商,都擁有基本結構相同的系統定時器。主要目的是給RTOS提供時鐘節拍做時間基準?;?b class='flag-5'>定時器
    發表于 11-23 18:21 ?19次下載
    STM32基于cubeMX<b class='flag-5'>實現</b><b class='flag-5'>定時器</b>點燈

    STM32定時器-基本定時器

    目錄定時器分類基本定時器功能框圖講解基本定時器功能時鐘源計數時鐘計數自動重裝載寄存
    發表于 11-23 18:21 ?31次下載
    STM32<b class='flag-5'>定時器</b>-基本<b class='flag-5'>定時器</b>

    定時器作用及實現定時器數據結構選取介紹1

    定時器在各種場景都需要用到,比如游戲的Buff實現,Redis中的過期任務,Linux中的定時任務等等。顧名思義,定時器的主要用途是執行定時
    的頭像 發表于 04-21 15:20 ?1059次閱讀
    <b class='flag-5'>定時器</b>作用及<b class='flag-5'>實現</b><b class='flag-5'>定時器</b><b class='flag-5'>數據結構</b>選取介紹1

    定時器作用及實現定時器數據結構選取介紹2

    定時器在各種場景都需要用到,比如游戲的Buff實現,Redis中的過期任務,Linux中的定時任務等等。顧名思義,定時器的主要用途是執行定時
    的頭像 發表于 04-21 15:20 ?1065次閱讀
    <b class='flag-5'>定時器</b>作用及<b class='flag-5'>實現</b><b class='flag-5'>定時器</b><b class='flag-5'>數據結構</b>選取介紹2

    STM32如何使用定時器實現微秒(us)級延時?

    如何使用定時器實現微秒級延時的步驟: 步驟 1:配置定時器 首先,需要選擇一個適合的定時器。大多數STM32微控制
    的頭像 發表于 11-06 11:05 ?5223次閱讀

    定時器設計實現

    返回ITimer類型的共享指針。其中ITimer類中定義了start和stop方法,用于啟動或停止當前定時器。 TimerManager還有一個內部類TimerMessageQueue用于實現
    的頭像 發表于 11-08 16:50 ?502次閱讀

    redis數據結構的底層實現

    Redis是一種內存鍵值數據庫,常用于緩存、消息隊列、實時數據分析等場景。它的高性能得益于其精心設計的數據結構和底層實現。本文將詳細介紹Redis常用的
    的頭像 發表于 12-05 10:14 ?515次閱讀