1. 程式人生 > 實用技巧 >定時器的設計的幾種思路

定時器的設計的幾種思路

原文:高效定時器的設計_青萍之末的部落格-CSDN部落格

文章目錄

一、timerfd

呼叫timerfd_create()建立一個時間檔案描述符即把時間變為檔案描述符,定時器超時則檔案描述符會變得可讀,這樣可以和epoll結合起來,統一處理I/O事件和超時事件。可以把timerfd和時間戳一起作為key,避免兩個定時器相同的情況

二、定時器的概念

網路程式設計中應用層的定時器是很有必要的,這可以讓服務端主動關閉時間很久的非活躍連線。另外一種解決方案是TCP的keepalive,但它只能檢測真正的死連線,即客端主機斷電,或者網線被拔掉這種情況

。如果客端連線上,但什麼都不做,keepalive是毫無辦法的,它只能一定時間後不斷的向客戶端傳送心跳包

在開發高效能伺服器中,定時器總是不可或缺的, 常見的定時器實現三種,分別是:排序連結串列,最小堆,時間輪
在這裡插入圖片描述

三、排序連結串列

升序定時器連結串列(使用雙向連結串列)將定時器按照超時時間做升序排序,心博函式從頭結點開始一次處理每個定時器,直到遇到一個尚未到期的定時器,然後呼叫定時器超時回撥函式,以執行定時任務。
在這裡插入圖片描述

這種定時器設計的缺點在於,新增一個定時器的複雜度是O(n),因為要保持有序性,所以需要遍歷連結串列插入到合適的位置。假設系統有大量的定時器(10W個)使用升序連結串列型的就會有效能問題。

四、最小堆(優先佇列)

堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於(或不小於)其左子節點和右子節點的值

由於堆是一種經過排序的完全二叉樹,因此在構建的時候需要對新插入的節點進行一些操作以使其符合堆的性質。這種操作就是節點的上浮與下沉。

由於定時器的觸發是由於時間到了,因此只有時間最短的定時器會首先被觸發,通過這個原理,我們可以採用最小堆,將按時間順序排序,堆頂元素是時間最短的定時器,因此只要判斷堆頂元素是否被觸發即可。只有堆頂定時器的時間到了,才會到其他時間較晚的定時器的時間。
在這裡插入圖片描述
【特點】:

  • 最小堆一般是採用堆的方式實現,元素訪問速度遠高於採用連結串列方式的紅黑樹,插入效能快紅黑樹好幾倍,但最小堆的刪除效能並不快於紅黑樹
  • 最小堆的最大缺點就在於必須是連續的記憶體

五、時間輪(環形佇列+雙向連結串列)

時間輪簡介:時間輪方案將現實生活中的時鐘概念引入到軟體設計中,主要思路是定義一個時鐘週期和步長(比如時鐘的一秒走一次),當指標每走一步的時候,會獲取當前時鐘刻度上掛載的任務並執行
在這裡插入圖片描述
  以上圖為例,假設一個格子為1秒,整個一圈表示的時間為12秒,此時需要新增5秒後執行的任務,則此時改任務一個放到第(1+5=6)的格子內,如果此時新增13秒後執行任務,此時該任務應該等轉完一圈後round為1 放到第二格子中,指標每轉動一個一格,獲取當前round為0的任務執行,格子上的其他任務round減1 。問題:當時間跨度很大,數量很大時,單層的時間輪造成的round很大,一個格子中鏈很長,所以衍生出多級時間輪的設計方案
在這裡插入圖片描述
  假設圖中每層輪子為20個格子,第一層輪子最小時間間隔為1ms,第二層為20ms,第三層為400ms,此時新增5ms後執行的任務,此時應該新增到第一層的第5格子中。如果此時新增445ms後執行的任務,則第一層表示的時間跨度不夠,第二層表示的時間跨度也不夠,第三層表示的時間跨度足夠,該任務應該放到第三層輪子第二格子中,該輪子指標指到第二格子中時,計算離任務啟動時間還有多長時間,慢慢將該任務移動到底層輪子上,最終任務到期執行。

【特點】:

  • 是任務的新增與移除,都是O(1)級的複雜度;
  • 不會佔用大量的資源;
  • 只需要有一個執行緒去推進時間輪就可以工作了。

五、紅黑樹(std::set自動排序)

在這裡插入圖片描述
  Nginx定時器實現的核心是使用一棵紅黑樹來儲存各個定時事件,每次迴圈的時候就從這棵樹裡找出超時的事件,然後一一觸發,完成定時任務操作。
  
在worker程序的每一次迴圈中都會呼叫ngx_process_events_and_timers函式,在該函式中就會呼叫處理定時器的函式ngx_event_expire_timers,每次該函式都不斷的從紅黑樹中取出時間值最小的,檢視他們是否已經超時,然後執行他們的函式,直到取出的節點的時間沒有超時為止

紅黑樹插入是最壞情況要比較2logN次(最高的高度)外加不超過兩次旋轉,最小堆最壞情況是logN次;紅黑樹刪除不需要比較只需要不超過3旋轉,查詢最小值需要遍歷logN

參考:
https://blog.csdn.net/FreeeLinux/article/details/54884771
https://blog.csdn.net/u010939285/article/details/80049412