固定Tick时间策略

 固定Tick时间:顾名思义就是指程序每次心跳的时间都是等长的、固定的。如图中的“图A”,Tick1和Tick2的时间是相等的,如果实际执行的比上次执行时间长(Run2 > Run1),则Sleep2 < Sleep1,同时满足等式:Tick1 = Tick2 = Run1 + Sleep1 = Run2 + Sleep2
Mangos-Zero
 mangos-zero项目中的逻辑服务进程mangosd的心跳函数采用的方法,当更新的处理时间Run1大于固定大小的tick时间时,下一个tick到来时不sleep直接执行Run2,实现代码如下:
   1: /// Heartbeat for the World
   2: void WorldRunnable::run()
   3: {
   4:     ///- Init new SQL thread for the world database
   5:     WorldDatabase.ThreadStart();     // let thread do safe mySQL requests (one connection call enough)
   6:     sWorld.InitResultQueue();
   7:  
   8:     uint32 realCurrTime = 0;
   9:     uint32 realPrevTime = WorldTimer::tick();
  10:  
  11:     uint32 prevSleepTime = 0;        // used for balanced full tick time length near WORLD_SLEEP_CONST
  12:  
  13:     ///- While we have not World::m_stopEvent, update the world
  14:     while (!World::IsStopped())
  15:     {
  16:         ++World::m_worldLoopCounter;
  17:         realCurrTime = WorldTimer::getMSTime();  //----------------(1)
  18:  
  19:         uint32 diff = WorldTimer::tick();        //--------------(2)
  20:  
  21:         sWorld.Update( diff );                   //--------------(3)
  22:         realPrevTime = realCurrTime;
  23:  
  24:         // diff (D0) include time of previous sleep (d0) + tick time (t0)
  25:         // we want that next d1 + t1 == WORLD_SLEEP_CONST
  26:         // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement
  27:         // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0
  28:         if (diff <= WORLD_SLEEP_CONST+prevSleepTime)    //----------------(4)
  29:         {
  30:             prevSleepTime = WORLD_SLEEP_CONST+prevSleepTime-diff;
  31:             ACE_Based::Thread::Sleep(prevSleepTime);
  32:         }
  33:         else
  34:             prevSleepTime = 0;
  35:  
  36:         #ifdef WIN32
  37:             if (m_ServiceStatus == 0) World::StopNow(SHUTDOWN_EXIT_CODE);
  38:             while (m_ServiceStatus == 2) Sleep(1000);
  39:         #endif
  40:     }
  41:  
  42:     sWorld.KickAll();                // save and kick all players
  43:     sWorld.UpdateSessions( 1 );      // real players unload required UpdateSessions call
  44:  
  45:     // unload battleground templates before different singletons destroyed
  46:     sBattleGroundMgr.DeleteAllBattleGrounds();
  47:  
  48:     sWorldSocketMgr->StopNetwork();
  49:  
  50:     sMapMgr.UnloadAll();             // unload all grids (including locked in memory)
  51:  
  52:     ///- End the database thread
  53:     WorldDatabase.ThreadEnd();       // free mySQL thread resources
  54: }
以上代码是游戏世界的主循环,看while循环里的代码,主要干下面几件事:
(1)从WorldTimer::getMSTime()得到一个uint32的值realCurrTime,realCurrTime是循环的(到增加到0xFFFFFFFF后,在增加就变成0),表示当前时间,单位是毫秒,是一个相对前一次tick的时间。
(2)使用WorldTimer::tick();计算上次tick到这次tick的时间差diff,该值理论上等于realCurrTime – realPrevTime
(3)sWorld.Update( diff );就是tick里的处理函数,游戏逻辑在这里得到更新处理。
(4)这里就是图所描述的,如果运行时间大于固定的tick时间,则不sleep继续占用CPU来处理更新,直到能在一个tick处理所有操作为止,这个时候才会sleep让出CPU时间片。
(5)WORLD_SLEEP_CONST就是固定的tick的时间长度,在这里是50ms
总结:现在可以回答本节前面的两个问题:在高负荷情况下mangos采用的方式提高服务器的响应速度,每个tick时间长度为50ms,也就是每秒钟更新20次,能满足更新的需求。
如果主逻辑循环是在调用epoll,那么可以利用epoll_wait的timeout参数进行代替sleep,实现如下:
        uint64_t prevSleepTime = 0;
        uint64_t curTime = 0;
        uint64_t prevTime = x_time::now_msec();
        while(!getTermnation())
        {
            curTime = x_time::now_msec();
            // t = tick , s = sleep 
            //diff = s0 + t0 , diff 是两次循环的间隔时间
            //希望一直保持 s1 + t1 = MAX_EPOLL_THREAD_TIMEOUT(100ms) , tick(t1)未知,但可以使用t0来作为参考值代替
            //s1 = MAX_EPOLL_THREAD_TIMEOUT - t1 = MAX_EPOLL_THREAD_TIMEOUT - t0 = MAX_EPOLL_THREAD_TIMEOUT - (diff - s0)
            uint64_t diff = 0;
            if(prevTime > curTime)
            {
                diff = 0xFFFFFFFFFFFFFFFF - (prevTime - curTime);
            }
            else
            {
                diff = curTime - prevTime;
            }
            //如果运行时间大于固定的tick时间,则不sleep继续占用CPU来处理更新,直到能在一个tick处理所有操作为止,这个时候才会sleep让出CPU时间片
            if(diff <= MAX_EPOLL_THREAD_TIMEOUT + prevSleepTime) // diff - s0 = t0 >= MAX_EPOLL_THREAD_TIMEOUT
            {
                prevSleepTime = MAX_EPOLL_THREAD_TIMEOUT - (diff - prevSleepTime);
            }
            else
            {
                prevSleepTime = 0;
            }
            prevTime = curTime;
            this->check_queue(epfd);
            int conns = epoll_wait(epfd,&events[0],MAX_EPOLL_EVENTS,prevSleepTime); //sleep
            if(getTermnation()) return true;
            for(int i=0;i<conns;i++) 
            {
                int fd = events[i].data.fd;
                x_tcp_task* task = (x_tcp_task*)events[i].data.ptr;
                if(events[i].events & EPOLLIN)
                {
                    task->ListenRecv();
                }
                else if(events[i].events & EPOLLOUT)
                {
                    task->ListenSend();
                }
            }
            this->update(diff);
        }
        return true;

![个人博客系统[SpringBoot+SpringMVC+MyBais]](https://img-blog.csdnimg.cn/6857e5eabf1e48b8a2811a373eda75fb.png)
















