libevent不僅支持io事件,同時還支持timeout事件與signal事件,這篇文件將分析libevent是如何組織timeout事件以及如何響應timeout事件。
1. min_heap
首先,event_base中有一個成員struct min_heap timeheap,這是一個最小堆,用來存儲timeout事件的結構之一。先來看一下它的定義:
typedef struct min_heap { struct event** p; size_t n, a; } min_heap_t;
p是一個可以動態擴展的指針數組,數組長度為a,n表示堆成員數量,堆成員是指向event結構的指針。libevent中最小堆相關的操作定義在minheap-internal.h源碼文件中,使用這些操作可以維持數組中的成員保持最小堆性質。
堆排序的依據是event結構中的成員ev_timeout,這個成員記錄了事件超時的絕對時間(即超時時間點)。根據最小堆性質,堆頂元素總是最早超時的事件。
2. event_base_loop
使用min_heap的堆頂元素,libevent總是可以知道從現在起到有事件超時所需的時間tv,這個操作在event_base_loop中調用timeout_next函數完成。libevent處理io事件的后端接口,如select,都有一個時間參數作為阻塞的最長時間,將tv作為后端接口的入參,那么event_base_loop在循環中總是會在有事件超時之前被喚醒。喚醒后再循環檢查min_heap的堆頂元素是否超時,超時則將它加入到event_base的待執行回調函數鏈表,這步操作在event_base_loop中調用timeout_process函數實現。如此即可實現超時事件的響應。
3. common_timeout_queues
僅使用min_heap成員,event_base已經足夠管理timeout事件了,但是當有太多的timeou事件時,維持最小堆性質會成為一個耗時的操作。為解決這個問題,libevent使用了一個叫做common_timeout_list的結構,將超時時間相近的事件放到一起,這些事件在min_heap中僅留一個代表,減少min_heap的成員數量。common_timeout_list結構的定義如下(源碼在event-internal.h中):
struct common_timeout_list { /* List of events currently waiting in the queue. */ struct event_list events; /* 'magic' timeval used to indicate the duration of events in this * queue. */ struct timeval duration; /* Event that triggers whenever one of the events in the queue is * ready to activate */ struct event timeout_event; /* The event_base that this timeout list is part of */ struct event_base *base; };
events是一個事件的雙向鏈表,連接了所有屬於這個common_timeout_list的超時事件,struct event結構中的成員ev_timeout_pos.ev_next_with_common_timeout正是用來構造這個鏈表;duration是一個帶有特殊標記的時間戳(后面解釋如何標記);timeout_event即是加入min_heap中的代表事件;base記錄了歸屬的event_base結構。
event_base中可以存在多個common_timeout_lsit結構,它使用common_timeout_queues進行管理,這是一個動態擴展的指針數組,每一個成員都指向一個common_timeout_list結構,n_common_timeouts_allocated表示動態數組的長度,n_common_timeouts代表有效的成員數目,common_timeout_queues的結構可以用圖3-1表示:
圖3-1 common_timeout_queues結構
- 首先,common_timeout_list中的事件是按超時時間順序排列的
- 然后,common_timout_lsit中有一個專職的事件timeout_event用來加入到min_heap中,它的超時時間是事件鏈表中最早的超時時間。timeout_event的回調函數也是特殊設計的,作用是遍歷它歸屬的common_timeout_list上的事件鏈表,將超時的事件加入到event_base的待執行回調函數鏈表上。timeout_event的優先級被設置為0,在event_base_loop處理待執行回調函數時會優先執行,在它的回調函數中處理的超時事件加入的待執行回調函數鏈表優先級較低,因此這些事件的回調函數還可以在同一次event_base_loop循環中被執行。
類似timeout_event這種特殊事件被稱為內部事件,類似的設計在libevent處理signal事件中也有用到。
common_timeout_list事件中timeval的特殊標記:
libevent設計了如下標記:
/** Mask used to get the real tv_usec value from a common timeout. */ #define COMMON_TIMEOUT_MICROSECONDS_MASK 0x000fffff #define MICROSECONDS_MASK COMMON_TIMEOUT_MICROSECONDS_MASK #define COMMON_TIMEOUT_IDX_MASK 0x0ff00000 #define COMMON_TIMEOUT_IDX_SHIFT 20 #define COMMON_TIMEOUT_MASK 0xf0000000 #define COMMON_TIMEOUT_MAGIC 0x50000000
所有common_timeout_list鏈表中的event的ev_timeout.tv_usec都會異或上值COMMON_TIMEOUT_MAGIC作為標識;並將這個common_timeout_lsit在common_timeout_queues數組中的索引記錄在第21bit至28bit中;真正的tv_usec值只使用了COMMON_TIMEOUT_MICROSECONDS_MASK標記的比特位。