posts - 14,  comments - 57,  trackbacks - 0
  最新换了个项目组,阅读代码后,发现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微秒。
这个时间还包括每个定时器的执行消耗,效率还是不错的。
posted on 2011-03-13 22:06 feixuwu 阅读(2085) 评论(0)  编辑 收藏 引用 所属分类: 游戏开发

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


<2011年3月>
272812345
6789101112
13141516171819
20212223242526
272829303112
3456789

文章转载请注明出处

常用链接

留言簿(11)

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜