最近項目中遇到一個問題,用ace框架起的定時器,跑著跑著,它不跑了,然後我依賴定時器所創建的任務也一直掛在那裡。查看系統軟件打印的日志發現,是ace的ACE_Reactor在run_reactor_event_loop函數中返回了-1,reactor的時間循環結束,從而導致定時器函數handle_timeout函數無法再被觸發。為了找出問題的具體原因,看了一把ace的源碼,雖然到目前為止,為何run_reactor_event_loop函數返回-1的原因還沒找到,但我卻意外的發現了ace驅動定時器的原理(僅限於linux平台)。
利用ACE_Reactor來創建一個定時器主要是調用scheldule函數,ACE_Reactor的schedule函數會調用它在linux下得工具類ACE_Select_Reactor_T的schedule函數,ACE_Select_Reactor_T類中有一個ACE_Timer_Queue_T的成員變量,這個變量中保存著該ACE_Reactor創建的所有定時器的信息,包括定時器的事件對象(ACE_Event_Handle為基類的子類對象,這個對象中的handle_timeout函數即定時器的觸發函數)、定時間隔、下次定時器被觸發的時間(絕對時間)等。ACE_Reactor進行事件循環時會調用select函數阻塞等待被激活的句柄,select函數的最後一個入參是一個描敘時間的結構體,如果在結構體描敘的時間裡還是沒有等待到一個激活的句柄,那麼函數將會返回0,退出等待。ACE_Reactor正是利用select的這個特性,即實現了IO事件循環的處理也實現定時器事件循環的處理。其具體的操作為:
1、從ACE_Timer_Queue_T隊列中找出最近會被觸發的時間器的觸發時間
2、將上述取到的時間減去當前時間,並將其作為select的最後一個參數傳入select中,
3、select函數返回後(要麼有IO句柄被激活,要麼到了需要運行定時器函數的時間),在ACE_Timer_queue_t中檢測是否有需要被觸發的定時器(觸發時間已到,即將運行時間-當前時間<=0?),依次修改這些觸發時間已到的定時器的下次運行的時間,並調用觸發函數;在句柄集合中查找是否有激活狀態的句柄,並進行處理(調用handle_*函數)
4、重復1、2、3的操作(循環)
這就是ace裡面定時器的實現原理,由此可見ace的定時器還是會存在很多缺陷的,例如,如果一個ACE_Reactor中起了幾個定時器,而某些定時器的觸發函數需要運行較長得時間,那麼必會導致一些定時器函數被觸發的時間不准確。