Windows下的高精度定时器实现及精确时刻获取

时间:2021-10-03 23:56:49
通讯、VOIP、视频等领域的很多核心技术对时间精度的要求非常高,比如数据采集、时间同步、媒体流平滑控制、拥塞算法等等,很多技术都是以毫秒为单位来进行计算和控制的。但是Windows设计之初并不是以实时系统为目标的,所以Windows系统的时间精度一直不高,实际最小单位是15ms左右,导致的结果就是所有Windows的时间、线程相关的操作都无法以1ms来实现精确控制。

 

受影响的操作包括Sleep、GetTickCount、_ftime等等。比如你调用Sleep(2),期待2ms之后线程自动唤醒,但是实际结果可能是15ms甚至2x ms的时候才会唤醒,对于简单应用来说影响不大,但是对于精度要求非常高的系统来说,这样的问题就是非常致命的了。

 

代码思路如下:

1、高精度定时器。使用Singleton模式挂起请求Sleep的线程并统一管理,后台使用Windows MultiMedia SDK的定期回调函数不断检测并回复到时的线程,超时时间与当前时间采用QueryPerformanceCounter/QueryPerformanceFrequency的高精度计时,确保整体功能可靠性。

2、精确时刻获取。由于可以获取到毫秒级别的_ftime与GetTickCount都受到Windows系统时间精度影响,最小单位只有15ms,所以需要借助QueryPerformanceCounter/QueryPerformanceFrequency进行准确计时。代码首先根据_ftime获取起始时刻的精确刻度,然后根据差量计算当前的精确时刻。

 

代码中的Singleton模式可以找到很多实现,因此本文不进行详述

代码(VS2005 c++编译)

1、高精度定时器

 

 

[c-sharp] view plaincopy
  1. #pragma once  
  2.  
  3. #include <Windows.h>  
  4. #include <list>  
  5. #include <akumaslab/system/singleton.hpp>  
  6.   
  7. namespace akumaslab{  
  8.     namespace time{  
  9.         using std::list;  
  10.   
  11.         class PreciseTimerProvider  
  12.         {  
  13.             struct WaitedHandle{  
  14.                 HANDLE threadHandle;  
  15.                 LONGLONG elapsed;//超时时间  
  16.             } ;  
  17.             typedef list< WaitedHandle > handle_list_type;  
  18.             typedef akumaslab::system::Singleton< PreciseTimerProvider > timer_type;  
  19.         public:  
  20.             PreciseTimerProvider(void):highResolutionAvailable(false), timerID(0)  
  21.             {  
  22.                 InitializeCriticalSection(&critical);  
  23.                 static LARGE_INTEGER systemFrequency;  
  24.                 if(0 != QueryPerformanceFrequency(&systemFrequency))  
  25.                 {  
  26.                     timeBeginPeriod(callbackInterval);  
  27.                     highResolutionAvailable = true;  
  28.                     countPerMilliSecond = systemFrequency.QuadPart/1000;  
  29.                     timerID = timeSetEvent(callbackInterval, 0, &PreciseTimerProvider::TimerFunc, NULL, TIME_PERIODIC);  
  30.                 }  
  31.             }  
  32.             //挂起当前线程  
  33.             //@milliSecond:超时时间,单位:毫秒  
  34.             bool suspendCurrentThread(int milliSecond)  
  35.             {  
  36.                 if(milliSecond <= 0)return false;  
  37.                 if (!highResolutionAvailable)return false;  
  38.                 HANDLE currentThreadHandle = GetCurrentThread();  
  39.                 HANDLE currentProcessHandle = GetCurrentProcess();  
  40.                 HANDLE realThreadHandle(0);  
  41.                 DuplicateHandle(currentProcessHandle, currentThreadHandle, currentProcessHandle, &realThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);  
  42.                 WaitedHandle item;  
  43.                 item.threadHandle = realThreadHandle;  
  44.                 LARGE_INTEGER now;  
  45.                 QueryPerformanceCounter(&now);  
  46.                 now.QuadPart += milliSecond * countPerMilliSecond;  
  47.                 item.elapsed = now.QuadPart;  
  48.                 EnterCriticalSection(&critical);  
  49.                 waitList.push_back(item);  
  50.                 LeaveCriticalSection(&critical);  
  51.                 //挂起线程  
  52.                 SuspendThread(realThreadHandle);  
  53.                 CloseHandle(realThreadHandle);  
  54.                 return true;  
  55.             }  
  56.             //恢复超时线程  
  57.             void resumeTimeoutThread()  
  58.             {  
  59.                 if (!highResolutionAvailable)return;  
  60.                 LARGE_INTEGER now;  
  61.                 QueryPerformanceCounter(&now);  
  62.                 EnterCriticalSection(&critical);  
  63.                 for (handle_list_type::iterator ir = waitList.begin(); ir != waitList.end(); )  
  64.                 {  
  65.                     WaitedHandle& waited = *ir;  
  66.                     if (now.QuadPart >= waited.elapsed)  
  67.                     {  
  68.                         ResumeThread(waited.threadHandle);  
  69.                         ir = waitList.erase(ir);  
  70.                         continue;  
  71.                     }  
  72.                     ir++;  
  73.                 }                                 
  74.                 LeaveCriticalSection(&critical);  
  75.             }  
  76.             ~PreciseTimerProvider(){  
  77.                 if (0 != timerID)  
  78.                 {  
  79.                     timeKillEvent(timerID);  
  80.                     timerID = 0;  
  81.                     timeEndPeriod(callbackInterval);  
  82.                 }  
  83.                 DeleteCriticalSection(&critical);  
  84.             }  
  85.         private:  
  86.   
  87.             static void CALLBACK TimerFunc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)  
  88.             {  
  89.                 static bool initialed = false;  
  90.                 if (!initialed)  
  91.                 {  
  92.                     if (initialWorkThread())  
  93.                     {  
  94.                         initialed = true;  
  95.                     }  
  96.                     else{  
  97.                         return;  
  98.                     }  
  99.                 }  
  100.                 timer_type::getRef().resumeTimeoutThread();  
  101.             }  
  102.             //调整定时器工作线程优先级  
  103.             static bool initialWorkThread()  
  104.             {  
  105.                 HANDLE realProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, _getpid());  
  106.                 if (NULL == realProcessHandle)  
  107.                 {  
  108.                     return false;  
  109.                 }  
  110.                 if (0 == SetPriorityClass(realProcessHandle, REALTIME_PRIORITY_CLASS))  
  111.                 {  
  112.                     CloseHandle(realProcessHandle);  
  113.                     return false;  
  114.                 }  
  115.                 HANDLE currentThreadHandle = GetCurrentThread();  
  116.                 HANDLE currentProcessHandle = GetCurrentProcess();  
  117.                 HANDLE realThreadHandle(0);  
  118.                 DuplicateHandle(currentProcessHandle, currentThreadHandle, currentProcessHandle, &realThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);  
  119.                 SetThreadPriority(realThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);  
  120.                 //必须关闭复制句柄  
  121.                 CloseHandle(realThreadHandle);  
  122.                 CloseHandle(realProcessHandle);  
  123.                 return true;  
  124.             }  
  125.         private:  
  126.             const static int callbackInterval = 1;  
  127.             CRITICAL_SECTION critical;  
  128.             MMRESULT timerID;  
  129.             LONGLONG countPerMilliSecond;  
  130.             bool highResolutionAvailable;  
  131.             handle_list_type waitList;  
  132.         };  
  133.         class PreciseTimer  
  134.         {  
  135.             typedef akumaslab::system::Singleton< PreciseTimerProvider > timer_type;  
  136.         public:  
  137.             static bool wait(int milliSecond)  
  138.             {  
  139.                 //static PreciseTimerProvider timer;  
  140.                 return timer_type::getRef().suspendCurrentThread(milliSecond);  
  141.             }  
  142.         };  
  143.     }  
  144. }  
 

DEMO

[cpp] view plaincopy
  1. int interval = 1;  
  2. int repeatCount = 50;  
  3. cout << getCurrentTime() << "test begin" << endl;  
  4. unit.reset();  
  5. for (int i = 0; i < repeatCount; i++)  
  6. {  
  7.     akumaslab::time::PreciseTimer::wait(interval);  
  8.     cout << getCurrentTime() << "/" << getNewTime() << " used " << unit.getPreciseElapsedTime() << " ms" << endl;  
  9.     unit.reset();  
  10. }  

 

2、精确时刻获取

[cpp] view plaincopy
  1. #pragma once  
  2.   
  3. #include <sys/timeb.h>  
  4. #include <time.h>  
  5. #include <Windows.h>  
  6. #include <akumaslab/system/singleton.hpp>  
  7.   
  8. namespace akumaslab{  
  9.     namespace time{  
  10.         struct HighResolutionTime  
  11.         {  
  12.             int year;  
  13.             int month;  
  14.             int day;  
  15.             int hour;  
  16.             int min;  
  17.             int second;  
  18.             int millisecond;  
  19.         };  
  20.         class CurrentTimeProvider  
  21.         {  
  22.         public:  
  23.             CurrentTimeProvider():highResolutionAvailable(false), countPerMilliSecond(0), beginCount(0)  
  24.             {  
  25.                 static LARGE_INTEGER systemFrequency;  
  26.                 if(0 != QueryPerformanceFrequency(&systemFrequency))  
  27.                 {  
  28.                     highResolutionAvailable = true;  
  29.                     countPerMilliSecond = systemFrequency.QuadPart/1000;  
  30.                     _timeb tb;  
  31.                     _ftime_s(&tb);  
  32.                     unsigned short currentMilli = tb.millitm;  
  33.                     LARGE_INTEGER now;  
  34.                     QueryPerformanceCounter(&now);  
  35.                     beginCount = now.QuadPart - (currentMilli*countPerMilliSecond);  
  36.                 }  
  37.             };  
  38.             bool getCurrentTime(HighResolutionTime& _time)  
  39.             {  
  40.                 time_t tt;  
  41.                 ::time(&tt);  
  42.                 tm now;  
  43.                 localtime_s(&now, &tt);  
  44.                 _time.year = now.tm_year + 1900;  
  45.                 _time.month = now.tm_mon + 1;  
  46.                 _time.day = now.tm_mday + 1;  
  47.                 _time.hour = now.tm_hour;  
  48.                 _time.min = now.tm_min;  
  49.                 _time.second = now.tm_sec;  
  50.                 if (!highResolutionAvailable)  
  51.                 {  
  52.                     _time.millisecond = 0;  
  53.                 }  
  54.                 else{  
  55.                     LARGE_INTEGER qfc;  
  56.                     QueryPerformanceCounter(&qfc);  
  57.                     _time.millisecond = (int)((qfc.QuadPart - beginCount)/countPerMilliSecond)%1000;  
  58.                 }  
  59.                 return true;  
  60.             }  
  61.         private:  
  62.             bool highResolutionAvailable;  
  63.             LONGLONG countPerMilliSecond;  
  64.             LONGLONG beginCount;  
  65.         };  
  66.         class CurrentTime  
  67.         {  
  68.         public:  
  69.             static bool get(HighResolutionTime& _time)  
  70.             {  
  71.                 return akumaslab::system::Singleton< CurrentTimeProvider >::getRef().getCurrentTime(_time);  
  72.             }  
  73.         };  
  74.     }  
  75. }  

DEMO:

[cpp] view plaincopy
  1. HighResolutionTime time;  
  2. CurrentTime::get(time);  
  3. const int size = 20;  
  4. char buf[size] = {0};  
  5. _snprintf_s(buf, size, size, "%02d:%02d %02d:%02d:%02d.%03d ", time.month, time.day, time.hour, time.min, time.second, time.millisecond);  

 

测试结果如下,下图是高精度计时器按1ms进行Sleep的结果,左侧为使用_ftime计时,右侧为使用精确时刻计时,总体来说,虽然无法达到100%可靠,但是相对原来的15ms已经有较大提升,期望Windows能够尽快提供真正的高精度时间管理技术

Windows下的高精度定时器实现及精确时刻获取