Windows精确时间获取方法
很多时候,我们需要计算两个运行过程间计算机需要耗时多少,一般情况下我们采用GetTickcount函数:它返回从操作系统启动到当前所经过的毫秒数,常常用来判断某个方法执行的时间,其函数原型是DWORD GetTickCount(void),返回值以32位的双字类型DWORD存储,因此可以存储的最大值是(2^32-1) ms约为49.71天,因此若系统运行时间超过49.71天时,这个数就会归0,MSDN中也明确的提到了:“Retrieves the number of milliseconds that have elapsed since the system was started, up to 49.7 days.”。因此,如果是编写服务器端程序,此处一定要万分注意,避免引起意外的状况(如需避免此种情况可使用Ctime类或者是系统API的SYSTEMTIME进行判断),但是这个函数并非实时发送,而是由系统每18ms发送一次,因此其最小精度为18ms。当需要有小于18ms的精度计算时,应使用StopWatch方法进行。
经过不断测试发现:
-
连续触发200次,实测下来,最小间隔在15ms。
-
实际状况应该是系统每秒触发64次,间隔在15、16ms之间波动
为了得到精确的时间我们采QueryPerformanceCounter这样的API,而QueryPerformanceCounter的使用有一些隐含的陷阱需要注意。
关于QueryPerformanceCounter
官方解释:https://msdn.microsoft.com/zh-cn/ms644904,用于高精度计时器时间读取,重点是执行成功返回非0值,精度是<1us的。多核CPU采用QueryPerformanceCounter的问题
程序中通常使用时间差定时地调用某种接口的情况,而时间差的使用有一个隐含因素,即时间是顺序累加的,当然我们通常的时间当然是累加的,不会出现停滞甚至倒转,而QueryPerformanceCounter的运行情况是依赖于CPU的,当CPU是多核时,在某一线程内调用QueryPerformanceCounter,线程会切换于不同的核心之间,这时候QueryPerformanceCounter返回值是不确定的,或者说这时候的计时器并不能保证是顺序累加的,相应地,当使用时间差时会出现负数或者0的情况,这显然不符合开发者的预期。
下面是一个采用QueryPerformanceCounter函数编写的类,可以用于多线程(实测,我已用于很多项目中):
TimerControl.h
```cpp
#pragma once
#define WM_USER_TIMER_PULSE WM_USER + 0x101
//产生自定义时长脉冲信号,以毫秒为单位
UINT ThreadCustomMilliSecondTime(LPVOID pParam);
class CTimerControl
{
public:
CTimerControl(void);
~CTimerControl(void);
//得到当前系统时间
double GetCurrTime(void);
//按指定时间延时,以毫秒为单位
void static DelaySpecTime(int specTime);
//控制是否发生脉冲信号
void SetSignalSend(bool isSend = true);
//停止脉冲信号源
void StopTimerThread();
//开始自定义脉冲线程
void BeginTimerThread(HWND hWnd);
//设定自定义脉冲时间
void SetTimerSpace(float customTime);
// 得到当前点的时间
double GetCurPotTimer(void);
};``
TimerControl.cpp文件
#include "StdAfx.h"
#include "TimerControl.h"
#include ".\timercontrol.h"
bool m_isContinue;
bool m_isSendMessage;
float m_timeSpace;
double m_curPotTimer;
LARGE_INTEGER m_iFrequency;
//产生自定义时长脉冲信号,以毫秒为单位
UINT ThreadTime(LPVOID pParam)
{
CTimerControl timerCtrl;
HWND hWnd = (HWND)pParam;
LARGE_INTEGER iCounter;
LONGLONG preTime;
LONGLONG aftTime;
double realTime;
QueryPerformanceCounter(&iCounter);
preTime = iCounter.QuadPart;
while(m_isContinue)
{
QueryPerformanceCounter(&iCounter);
aftTime = iCounter.QuadPart;
realTime = ((double)(aftTime - preTime) / (double)m_iFrequency.QuadPart) * 10000000;
m_curPotTimer = (double)iCounter.QuadPart / (double)m_iFrequency.QuadPart;
if((int)(realTime + 0.5) >= m_timeSpace)
{
preTime = aftTime;
if (m_isSendMessage)
PostMessage(hWnd, WM_USER_TIMER_PULSE, 0, 0);
}
}
return 0;
}
CTimerControl::CTimerControl(void)
{
QueryPerformanceFrequency(&m_iFrequency);
}
CTimerControl::~CTimerControl(void)
{
m_timeSpace = 9999999;
m_isSendMessage = false;
m_isContinue = false;
}
//得到当前系统时间
double CTimerControl::GetCurrTime(void)
{
double currTime;
LARGE_INTEGER iCounter;
QueryPerformanceCounter(&iCounter);
currTime = (double)iCounter.QuadPart / (double)m_iFrequency.QuadPart;
return currTime;
}
//按指定时间延时,以毫秒为单位
void CTimerControl::DelaySpecTime(int specTime)
{
LARGE_INTEGER iCounter, iFrequency;
LONGLONG preTime;
LONGLONG aftTime;
double realTime;
QueryPerformanceFrequency(&iFrequency);
QueryPerformanceCounter(&iCounter);
preTime = iCounter.QuadPart;
while(true)
{
QueryPerformanceCounter(&iCounter);
aftTime = iCounter.QuadPart;
realTime = ((double)(aftTime - preTime) / (double)iFrequency.QuadPart) * 10000000;
if((int)(realTime + 0.5) >= specTime * 9990)
return;
}
}
//控制是否发生脉冲信号
void CTimerControl::SetSignalSend(bool isSend)
{
m_isSendMessage = isSend;
}
//停止脉冲信号源
void CTimerControl::StopTimerThread()
{
m_isSendMessage = false;
m_isContinue = false;
DelaySpecTime(200);
}
//开始时间脉冲线程
void CTimerControl::BeginTimerThread(HWND hWnd)
{
m_isContinue = true;
m_isSendMessage = false;
AfxBeginThread(ThreadTime, hWnd);
}
//设定自定义脉冲时间
void CTimerControl::SetTimerSpace(float customTime)
{
//m_timeSpace = customTime * 9992 * (float)0.65;
m_timeSpace = customTime * 9992;
}
// 得到当前点的时间
double CTimerControl::GetCurPotTimer(void)
{
::GetTickCount();
return m_curPotTimer;
}