最新換了個項目組,閱讀代碼后,發現Server端代碼居然沒有事件和定時器。由于沒有事件,所以各個模塊代碼互相調用的地方特別多,導致代碼結構混亂,所有代碼都放在一塊,亂成一鍋粥了。
沒有定時器,所有需要定時的任務,都只能添加類似OnUpdate的函數,在主循環的時候執行。定時需求少的時候,看不出明顯的問題,但是一旦這種需求多了,尤其是很多內部對象有定時需求的時候,
這個問題就比較明顯了,寫好了OnUpdate后,還要建立一條從主循環MainLoop到自身OnUpdate的調用鏈。
事件其實就是一個廣播和訂閱的關系,Delegate就是實現這樣一套機制的利器,目前Delegate的實現主要有2種,一種是CodeProject上的一個FastDelegate實現,另外一個比較典型的實現就是boost的
實現了,無論采取哪種實現方案,實現難度都不算太大。
Server當前框架對定時器無任何支持,只有一個DoMainLoop的函數可以派生來運行自己的定時邏輯。
我原來都是用的ACE封裝的組件,用了一段時間也沒發現明顯問題,不過ACE的定時器不太適合在這個新項目用,主要原因有如下幾點:
1、ACE庫太大了,不想僅僅為了定時器引入一個這么龐大的庫。
2、ACE的定時器需要額外啟動一個定時器線程,定時任務是在定時器線程跑的,而我們的項目邏輯其實是在單個線程運行的,如果直接采用ACE定時器,會給邏輯帶來額外的復雜度。由于整個邏輯線程的框架是公共模塊,手頭也沒有代碼,所以將定時器線程的任務發送到主邏輯線程運行也是不可行的。
3、ACE的定時器有很多種,TIMER_QUEUE、TIMER_WHELL、TIMER_HEAP等,個人感覺這些定時器的插入、取消操作都比較耗時,加以改裝放到主線程run的帶價將會很大。
其實linux內核就有一個比較高性能的定時器,代碼在kernel/Timer.c里, 2.6內核的定時器代碼更是簡潔。
linux的定時任務都是以jiffie
為單位的,linux將所有定時任務分為5個階梯,
struct tvec {
struct list_head vec[TVN_SIZE];
};
struct tvec_root {
struct list_head vec[TVR_SIZE];
};
struct tvec_base {
spinlock_t lock;
struct timer_list *running_timer;
unsigned long timer_jiffies;
struct tvec_root tv1;
struct tvec tv2;
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
} ____cacheline_aligned;
對一個新的定時任務,處理方法如下:
static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
{
unsigned long expires = timer->expires;
unsigned long idx = expires - base->timer_jiffies;
struct list_head *vec;
if (idx < TVR_SIZE) {
int i = expires & TVR_MASK;
vec = base->tv1.vec + i;
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
int i = (expires >> TVR_BITS) & TVN_MASK;
vec = base->tv2.vec + i;
} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
vec = base->tv3.vec + i;
} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
vec = base->tv4.vec + i;
} else if ((signed long) idx < 0) {
/*
* Can happen if you add a timer with expires == jiffies,
* or you set a timer to go off in the past
*/
vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
} else {
int i;
/* If the timeout is larger than 0xffffffff on 64-bit
* architectures then we use the maximum timeout:
*/
if (idx > 0xffffffffUL) {
idx = 0xffffffffUL;
expires = idx + base->timer_jiffies;
}
i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
vec = base->tv5.vec + i;
}
/*
* Timers are FIFO:
*/
list_add_tail(&timer->entry, vec);
}
從上可以看到Linux對定時器的處理:對即將在TVR_SIZE
個jiffies內到達的定時任務,將它掛到第一組tv1
下,具體就是掛到expires & TVR_MASK
對應的列表上去。
同一個jiffies到達的定時器是掛在同一個鏈表的。
同理,掛到第二個組的是 到期時間小于 1 << (TVR_BITS + TVN_BITS) jiffies的。
掛到第三個組的是 到期時間小于1 << (TVR_BITS + 2 * TVN_BITS)
jiffies的。
掛到第四個組的是 到期時間小于 1 << (TVR_BITS + 3 * TVN_BITS)
jiffies的。
超過1 << (TVR_BITS + 3 * TVN_BITS) 的掛到第五組。
這樣,所有到期的任務都會在第一組。任何時刻都可以直接通過當前jiffies&TVR_SIZE
來找到需要運行的定時器任務列表,定時器的插入效率就是O(1)。
下面是定時器的運行代碼:
static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
/* cascade all the timers from tv up one level */
struct timer_list *timer, *tmp;
struct list_head tv_list;
list_replace_init(tv->vec + index, &tv_list);
/*
* We are removing _all_ timers from the list, so we
* don't have to detach them individually.
*/
list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
BUG_ON(tbase_get_base(timer->base) != base);
internal_add_timer(base, timer);
}
return index;
}
#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)
/**
* __run_timers - run all expired timers (if any) on this CPU.
* @base: the timer vector to be processed.
*
* This function cascades all vectors and executes all expired timer
* vectors.
*/
static inline void __run_timers(struct tvec_base *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock);
while (time_after_eq(jiffies, base->timer_jiffies)) {
struct list_head work_list;
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK;
/*
* Cascade timers:
*/
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
++base->timer_jiffies;
list_replace_init(base->tv1.vec + index, &work_list);
while (!list_empty(head)) {
void (*fn)(unsigned long);
unsigned long data;
timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
timer_stats_account_timer(timer);
set_running_timer(base, timer);
detach_timer(timer, 1);
spin_unlock_irq(&base->lock);
{
int preempt_count = preempt_count();
fn(data);
if (preempt_count != preempt_count()) {
printk(KERN_ERR "huh, entered %p "
"with preempt_count %08x, exited"
" with %08x?\n",
fn, preempt_count,
preempt_count());
BUG();
}
}
spin_lock_irq(&base->lock);
}
}
set_running_timer(base, NULL);
spin_unlock_irq(&base->lock);
}
當第一組運行完一輪后,需要將tv2的一組新的定時任務加到第一組。這就好比時鐘的指針,秒針運行一圈后,分針步進一格,后續的調整都是類似。
cascade
就是負責將下一組的定時任務添加到前面的任務階梯。只有當第一輪的定時任務全部運行完畢后,才會需要從第二輪調入新的任務,只有第二級別的任務都調入完畢后,才需要從第三輪的定時任務調入新的任務:
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
這就是負責調整的代碼,相當的簡潔。
參照上述代碼實現一個定時器后,加入4000個定時任務:
for(int i = 1; i < 4000; i++)
{
g_TimerHandle[i] = g_timerManager.setTimer(&tmpSink1, i, i*10, "ss");
}
從10毫秒到4000*10毫秒,運行后,測試下性能,
函數名 執行次數 最小時間 平均時間 最大時間
TimerManager::runTimer 2170566 10 10 3046
可以看到,除了個別時間是因為線程切換導致數據比較大外,平均每次運行runTimer的時間是10微秒。
這個時間還包括每個定時器的執行消耗,效率還是不錯的。
只有注冊用戶登錄后才能發表評論。 | ||
【推薦】100%開源!大型工業跨平臺軟件C++源碼提供,建模,組態!
![]() |
||
相關文章:
|
||
網站導航:
博客園
IT新聞
BlogJava
博問
Chat2DB
管理
|
||
|
| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
27 | 28 | 1 | 2 | 3 | 4 | 5 | |||
6 | 7 | 8 | 9 | 10 | 11 | 12 | |||
13 | 14 | 15 | 16 | 17 | 18 | 19 | |||
20 | 21 | 22 | 23 | 24 | 25 | 26 | |||
27 | 28 | 29 | 30 | 31 | 1 | 2 | |||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
文章轉載請注明出處
常用鏈接
留言簿(11)
隨筆分類
隨筆檔案
- 2013年12月 (1)
- 2012年7月 (1)
- 2012年2月 (1)
- 2011年5月 (1)
- 2011年4月 (1)
- 2011年3月 (2)
- 2010年9月 (1)
- 2010年8月 (1)
- 2010年7月 (5)
搜索
最新評論

- 1.?re: linux下PageHeap
- 博主經驗好豐富,現在過了3年了。看前輩的文章,抽絲剝繭。正好昨天發現page heap不知道為什么?謝了。
- --thinkstream
- 2.?re: select 和 epoll[未登錄]
-
@tanxw
windows 下也是 1024 - --happy
- 3.?re: select 和 epoll
- windows下select又有連接數限制
- --滄海笑
- 4.?re: 劍3資源格式分析(僅用于學習和技術研究)(二)
- 評論內容較長,點擊標題查看
- --得一帥
- 5.?re: 推薦一個跨平臺內存分配器
- 在windows下怎么做堆檢測呢?
- --leehark