如何做到精确延时?

gdstx 2013-01-05 02:08:35
由于系统要求做到0.01毫秒以上的精度延时,我使用QueryPerformanceCounter(), 大概代码如下:

LARGE_INTEGER Frequency;
LARGE_INTEGER StartTicks, CurrentTicks;

QueryPerformanceFrequency(&Frequency);

QueryPerformanceCounter(&StartTicks);

do
{
//Sleep(1);
QueryPerformanceCounter(¤tTicks);
}while(CurrentTicks - StartTitcks > ...);

QueryPerformanceCounter()是可以获得精确计时, 但是延时如何做到呢?
如果使用Sleep(), 那使用QueryPerformanceCounter()就没有任何意义了, 因为Sleep()精度连1ms都做不到.
如果不使用Sleep(), CPU占用100%, 也没有意义.
请问我该如何应用QueryPerformanceCounter?
...全文
713 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhangyihu321 2013-01-09
  • 打赏
  • 举报
回复
多媒体定时器 或 APC调用
BeanJoy 2013-01-08
  • 打赏
  • 举报
回复
引用 18 楼 gdstx 的回复:
引用 16 楼 BeanJoy 的回复:引用 14 楼 gdstx 的回复: 引用 11 楼 BeanJoy 的回复:windows下有多种定时器,可尝试用定时器来延时,但可惜的是这些定时器时间分辨率都不太高,多是以milliseconds为单位,最大分辨率1milliseconds,若想更精确的,就得另想办法。 我的想法是,如果需要的分辨率为0.01millisec……
#define USE 1的方案可能不行,占用CPU不行。其他方案占用CPU倒不高。
gdstx 2013-01-08
  • 打赏
  • 举报
回复
引用 16 楼 BeanJoy 的回复:
引用 14 楼 gdstx 的回复: 引用 11 楼 BeanJoy 的回复:windows下有多种定时器,可尝试用定时器来延时,但可惜的是这些定时器时间分辨率都不太高,多是以milliseconds为单位,最大分辨率1milliseconds,若想更精确的,就得另想办法。 我的想法是,如果需要的分辨率为0.01milliseconds,则可以创建100个定时器,每个定时器间隔0.01mill……
你的机器应该是双核的,所以50%, 如果单核就100%了. 这还只是跑1个定时器, 如果跑多个, 并且每个延时都不同, 就不行了
九州剑王 2013-01-07
  • 打赏
  • 举报
回复
引用 15 楼 gdstx 的回复:
引用 7 楼 hfz8867879 的回复:引用 5 楼 gdstx 的回复: 引用 1 楼 hfz8867879 的回复:内核延时都可以精确到微秒的吧,你可以通过内核的驱动程序去弄计时的地方 具体是怎么搞的? 0.05MS的精度我觉得是有的吧,你把你的sleep的调用改成驱动的通信就好,比如驱动里设置个EVENT,超时50US,然后你的应用程序等待这个EV……
仅仅涉及定时和事件的驱动很好写的,可以再框架里加入这些功能,很快应该
BeanJoy 2013-01-06
  • 打赏
  • 举报
回复
引用 14 楼 gdstx 的回复:
引用 11 楼 BeanJoy 的回复:windows下有多种定时器,可尝试用定时器来延时,但可惜的是这些定时器时间分辨率都不太高,多是以milliseconds为单位,最大分辨率1milliseconds,若想更精确的,就得另想办法。 我的想法是,如果需要的分辨率为0.01milliseconds,则可以创建100个定时器,每个定时器间隔0.01millisecond……
我写的是期望,结果可能不那么令人满意。 我在我自己的机子上测试,可以降低CPU的使用率,至少一直是50%左右,但在工作机上测试,效果不明显,依然在50%左右徘徊。
gdstx 2013-01-06
  • 打赏
  • 举报
回复
引用 7 楼 hfz8867879 的回复:
引用 5 楼 gdstx 的回复: 引用 1 楼 hfz8867879 的回复:内核延时都可以精确到微秒的吧,你可以通过内核的驱动程序去弄计时的地方 具体是怎么搞的? 0.05MS的精度我觉得是有的吧,你把你的sleep的调用改成驱动的通信就好,比如驱动里设置个EVENT,超时50US,然后你的应用程序等待这个EVENT就达到了延时的目的吧
这种方法应该能行, 驱动的NdisMSleep(), NdisStallExecution()介绍可以达到微秒级, 只是我还没有做过驱动, 看来得先入个门, 试试能否解决.
gdstx 2013-01-06
  • 打赏
  • 举报
回复
引用 11 楼 BeanJoy 的回复:
windows下有多种定时器,可尝试用定时器来延时,但可惜的是这些定时器时间分辨率都不太高,多是以milliseconds为单位,最大分辨率1milliseconds,若想更精确的,就得另想办法。 我的想法是,如果需要的分辨率为0.01milliseconds,则可以创建100个定时器,每个定时器间隔0.01milliseconds(可利用QueryPerformanceCounter),这样,……
思路不错. // 这儿期望可以短暂延时,降低CPU使用率 WaitForSingleObject(g_hEvent, 0); 只是以上语句并不能降低CPU占用
AHAU10 2013-01-06
  • 打赏
  • 举报
回复
当然是用定时器啦
  • 打赏
  • 举报
回复
那就只有另一条路可走了 用一个单片机定时,然后发硬件中断过来
BeanJoy 2013-01-06
  • 打赏
  • 举报
回复
这个问题研究到早上2点多,有点儿心得,应该能达到楼主的要求。 昨上回去再修改修改代码,帖上来。
无脸男371545207 2013-01-06
  • 打赏
  • 举报
回复
windows不是实时操作系统,怎么精确延时呢
傻X 2013-01-06
  • 打赏
  • 举报
回复
如果你长时间开启为微妙级定时器,那么在单核CPU的情况下必定是100%的,除非多核。 理由是微软没有提供相应的API,我们用的是CPU计算效率来实现的。该方法无比消耗CPU。这就是原因 附上微妙级定时器代码:

LARGE_INTEGER litmp;
      LONGLONG QPart1,QPart2;
      double dfMinus, dfFreq, dfTim;
      QueryPerformanceFrequency(&litmp);
      dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
      QueryPerformanceCounter(&litmp);
      QPart1 = litmp.QuadPart;// 获得初始值
      do
      {
         QueryPerformanceCounter(&litmp);
         QPart2 = litmp.QuadPart;//获得中止值
         dfMinus = (double)(QPart2-QPart1);
         dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
      }while(dfTim<0.000001);
BeanJoy 2013-01-06
  • 打赏
  • 举报
回复
windows下有多种定时器,可尝试用定时器来延时,但可惜的是这些定时器时间分辨率都不太高,多是以milliseconds为单位,最大分辨率1milliseconds,若想更精确的,就得另想办法。 我的想法是,如果需要的分辨率为0.01milliseconds,则可以创建100个定时器,每个定时器间隔0.01milliseconds(可利用QueryPerformanceCounter),这样,100个定时器循环定时,大致就能做到0.01milliseconds的分辨率。但是注意,并不是每种定时器类型都可创建我们需要的个数。我写了测试代码,如下,供参考,代码都很简单,因此就没太多注释:

#define _WIN32_WINNT 0x0500 

#include <Windows.h>
#include <stdio.h>

#define ERASE_TIME_IN_MILLISEC(c, s, f)\
	(((double)c.QuadPart - s.QuadPart) / f.QuadPart * 1000)

#define USE				2
#define DELAY_TIME		100		// microseconds (0.001 milliseconds)
#define TIMMER_COUNT	((1000 / DELAY_TIME) * 2)

LARGE_INTEGER g_lnFrequency = {0};
LARGE_INTEGER g_lnStartPerfmcCount= {0};
LARGE_INTEGER g_lnCurrPerfmcCount = {0};

HANDLE g_hEvent = NULL;

void CALLBACK TimerFunction(UINT wTimerID, UINT msg,
							DWORD dwUser, DWORD dw1, DWORD dw2)
{
	QueryPerformanceCounter(&g_lnCurrPerfmcCount);
	printf("TimerID : %d  erase time : %lf\n", wTimerID,
		ERASE_TIME_IN_MILLISEC(g_lnCurrPerfmcCount, g_lnStartPerfmcCount, g_lnFrequency));
	g_lnStartPerfmcCount.QuadPart = g_lnCurrPerfmcCount.QuadPart;
}

void CALLBACK TimerProc(void* lpParametar,
						BOOLEAN TimerOrWaitFired)
{
//	int *p = (int *)lpParametar;
	QueryPerformanceCounter(&g_lnCurrPerfmcCount);
// 	printf("ThreadID : %d  para : %4d  erase time : %lf\n", GetCurrentThreadId(), *p,
// 	   ERASE_TIME_IN_MILLISEC(g_lnCurrPerfmcCount, g_lnStartPerfmcCount, g_lnFrequency));
	printf("erase time : %lf\n", 
		ERASE_TIME_IN_MILLISEC(g_lnCurrPerfmcCount, g_lnStartPerfmcCount, g_lnFrequency));
	g_lnStartPerfmcCount.QuadPart = g_lnCurrPerfmcCount.QuadPart;
}

int main(int, char **, char **)
{
	QueryPerformanceFrequency(&g_lnFrequency);

	g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

#if 1 == USE
	LARGE_INTEGER lnFrequency = {0};
	LARGE_INTEGER lnStartPerfmcCount= {0};
	LARGE_INTEGER lnCurrPerfmcCount = {0};
	lnFrequency.QuadPart = g_lnFrequency.QuadPart;

	do 
	{
		QueryPerformanceCounter(&lnStartPerfmcCount);

		// 这儿期望可以短暂延时,降低CPU使用率
		WaitForSingleObject(g_hEvent, 0);

		QueryPerformanceCounter(&lnCurrPerfmcCount);
		
		printf("wait time : %lf milliseconds\n", 
			ERASE_TIME_IN_MILLISEC(lnCurrPerfmcCount, lnStartPerfmcCount, lnFrequency));
	} while (TRUE);
	
#elif 2 == USE
	LARGE_INTEGER lnFrequency = {0};
	LARGE_INTEGER lnStartPerfmcCount = {0};
	LARGE_INTEGER lnStartPerfmcCountTmp = {0};
	LARGE_INTEGER lnFirstStartPerfmcCount = {0};
	LARGE_INTEGER lnLastStartPerfmcCount = {0};
	LARGE_INTEGER lnCurrPerfmcCount = {0};
	lnFrequency.QuadPart = g_lnFrequency.QuadPart;

	TIMECAPS tc;
    timeGetDevCaps(&tc, sizeof(TIMECAPS));
    UINT unResolution = min(max(tc.wPeriodMin, 0), tc.wPeriodMax);
    timeBeginPeriod(unResolution);

	// 首次创建定时器较耗时,因此单独先创建一个定时器,但不使用
	timeKillEvent(
		timeSetEvent(1, 0, (LPTIMECALLBACK)g_hEvent, NULL, TIME_ONESHOT | TIME_CALLBACK_EVENT_SET));

	MMRESULT mmrTimmer = NULL;
	MMRESULT mmrTimmerTmp = NULL;

	timeSetEvent(1, 0, (LPTIMECALLBACK)g_hEvent, NULL, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET);
	QueryPerformanceCounter(&lnStartPerfmcCount);
	lnFirstStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
	lnLastStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
	do 
	{
		mmrTimmer = timeSetEvent(
			1, 0, (LPTIMECALLBACK)g_hEvent, NULL, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET);

		QueryPerformanceCounter(&lnCurrPerfmcCount);
Judge:
		if (ERASE_TIME_IN_MILLISEC(lnCurrPerfmcCount, lnStartPerfmcCount, lnFrequency) * 1000 < DELAY_TIME)
		{
			if (NULL != mmrTimmerTmp)
			{
				timeKillEvent(mmrTimmerTmp);
			}
			
			mmrTimmerTmp = mmrTimmer;
			lnStartPerfmcCountTmp.QuadPart = lnCurrPerfmcCount.QuadPart;
		}
		else
		{
			if (NULL != mmrTimmerTmp)
			{
				mmrTimmerTmp = NULL;
				lnStartPerfmcCount.QuadPart = lnStartPerfmcCountTmp.QuadPart;
				lnLastStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
				goto Judge;
			}
			else
			{
				lnStartPerfmcCount.QuadPart = lnCurrPerfmcCount.QuadPart;
				lnLastStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
			}
		}
	} while (ERASE_TIME_IN_MILLISEC(lnLastStartPerfmcCount, lnFirstStartPerfmcCount, lnFrequency) <= 1.0);

	if (NULL != mmrTimmerTmp)
	{
		timeKillEvent(mmrTimmerTmp);
		mmrTimmerTmp = NULL;
	}

	do 
	{
		QueryPerformanceCounter(&lnStartPerfmcCount);
		
		WaitForSingleObject(g_hEvent, INFINITE);
		
		QueryPerformanceCounter(&lnCurrPerfmcCount);
		
		printf("wait time : %lf milliseconds\n",
			ERASE_TIME_IN_MILLISEC(lnCurrPerfmcCount, lnStartPerfmcCount, lnFrequency));
	} while (TRUE);

#elif 3 == USE
	int nTimmerExist[TIMMER_COUNT] = {0};
	int nTimmerLength = DELAY_TIME / 2;
	int nTimmerCount = 0;

	LARGE_INTEGER lnFrequency = {0};
	LARGE_INTEGER lnStartPerfmcCount = {0};
	LARGE_INTEGER lnFirstStartPerfmcCount = {0};
	LARGE_INTEGER lnCurrPerfmcCount = {0};
	lnFrequency.QuadPart = g_lnFrequency.QuadPart;
	
	int nIndex = 0;
	MMRESULT mmrTimmer = NULL;
	do 
	{
// 		mmrTimmer = timeSetEvent(
// 			1, 0, (LPTIMECALLBACK)g_hEvent, NULL, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET);
		mmrTimmer = timeSetEvent(
			1, 0, (LPTIMECALLBACK)TimerFunction, NULL, TIME_PERIODIC);
		QueryPerformanceCounter(&lnCurrPerfmcCount);
		if (0 == lnFirstStartPerfmcCount.QuadPart)
		{
			nTimmerExist[0] = 1;
			lnFirstStartPerfmcCount.QuadPart = lnCurrPerfmcCount.QuadPart;
		}
		else
		{
			nIndex = (lnCurrPerfmcCount.QuadPart - lnFirstStartPerfmcCount.QuadPart) / nTimmerLength 
				% TIMMER_COUNT;
			if (0 == nTimmerExist[nIndex])
			{
				nTimmerExist[nIndex] = 1;
			}
			else
			{
				timeKillEvent(mmrTimmer);
				continue;
			}
		}
		nTimmerCount++;
	} while (nTimmerCount < TIMMER_COUNT);

	do 
	{
		QueryPerformanceCounter(&lnStartPerfmcCount);
		
		WaitForSingleObject(g_hEvent, INFINITE);
		
		QueryPerformanceCounter(&lnCurrPerfmcCount);
		
		printf("wait time : %lf milliseconds\n",
			ERASE_TIME_IN_MILLISEC(lnCurrPerfmcCount, lnStartPerfmcCount, lnFrequency));
	} while (TRUE);

#elif 4 == USE
	LARGE_INTEGER lnFrequency = {0};
	LARGE_INTEGER lnStartPerfmcCount = {0};
	LARGE_INTEGER lnStartPerfmcCountTmp = {0};
	LARGE_INTEGER lnFirstStartPerfmcCount = {0};
	LARGE_INTEGER lnLastStartPerfmcCount = {0};
	LARGE_INTEGER lnCurrPerfmcCount = {0};
	lnFrequency.QuadPart = g_lnFrequency.QuadPart;
	
	HANDLE hTimmerTmp = NULL;
	HANDLE hTimmer = NULL;
	::CreateTimerQueueTimer(
		&hTimmer,
		NULL,
		TimerProc,
		NULL,
		0,
		0,
		WT_EXECUTEINTIMERTHREAD);
	DeleteTimerQueueTimer(NULL, hTimmer, NULL);
	
	::CreateTimerQueueTimer(
		&hTimmer,
		NULL,
		TimerProc,
		NULL,
		0,
		1,
		WT_EXECUTEINTIMERTHREAD);
	QueryPerformanceCounter(&lnStartPerfmcCount);
	lnFirstStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
	lnLastStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
	do 
	{
		::CreateTimerQueueTimer(
			&hTimmer,
			NULL,
			TimerProc,
			NULL,
			0,
			1,
			WT_EXECUTEINTIMERTHREAD);
		
		QueryPerformanceCounter(&lnCurrPerfmcCount);
Judge2:
		if (ERASE_TIME_IN_MILLISEC(lnCurrPerfmcCount, lnStartPerfmcCount, lnFrequency) * 1000 < DELAY_TIME)
		{
			if (NULL != hTimmerTmp)
			{
				DeleteTimerQueueTimer(NULL, hTimmer, NULL);
			}
			
			hTimmerTmp = hTimmer;
			lnStartPerfmcCountTmp.QuadPart = lnCurrPerfmcCount.QuadPart;
		}
		else
		{
			if (NULL != hTimmerTmp)
			{
				hTimmerTmp = NULL;
				lnStartPerfmcCount.QuadPart = lnStartPerfmcCountTmp.QuadPart;
				lnLastStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
				goto Judge2;
			}
			else
			{
				lnStartPerfmcCount.QuadPart = lnCurrPerfmcCount.QuadPart;
				lnLastStartPerfmcCount.QuadPart = lnStartPerfmcCount.QuadPart;
			}
		}
	} while (ERASE_TIME_IN_MILLISEC(lnLastStartPerfmcCount, lnFirstStartPerfmcCount, lnFrequency) <= 1.0);
	
	if (NULL != hTimmerTmp)
	{
		DeleteTimerQueueTimer(NULL, hTimmerTmp, NULL);
		hTimmerTmp = NULL;
	}
	
	do 
	{
		QueryPerformanceCounter(&lnStartPerfmcCount);
		
		WaitForSingleObject(g_hEvent, INFINITE);
		
		QueryPerformanceCounter(&lnCurrPerfmcCount);
		
		printf("wait time : %lf milliseconds\n",
			ERASE_TIME_IN_MILLISEC(lnCurrPerfmcCount, lnStartPerfmcCount, lnFrequency));
	} while (TRUE);

#elif 5 == USE
	int nTimmerExist[TIMMER_COUNT] = {0};
	int nTimmerLength = DELAY_TIME / 2;
	int nTimmerCount = 0;
	
	LARGE_INTEGER lnFrequency = {0};
	LARGE_INTEGER lnStartPerfmcCount = {0};
	LARGE_INTEGER lnFirstStartPerfmcCount = {0};
	LARGE_INTEGER lnCurrPerfmcCount = {0};
	lnFrequency.QuadPart = g_lnFrequency.QuadPart;
	
	HANDLE hTimmer;
	int nIndex = 0;
	do 
	{
		::CreateTimerQueueTimer(
			&hTimmer,
			NULL,
			TimerProc,
			&nTimmerExist[nTimmerCount],
			0,
			1,
			WT_EXECUTEINTIMERTHREAD);
		QueryPerformanceCounter(&lnCurrPerfmcCount);
		if (0 == lnFirstStartPerfmcCount.QuadPart)
		{
			nTimmerExist[0] = 1;
			lnFirstStartPerfmcCount.QuadPart = lnCurrPerfmcCount.QuadPart;
		}
		else
		{
			nIndex = (lnCurrPerfmcCount.QuadPart - lnFirstStartPerfmcCount.QuadPart) / nTimmerLength 
				% TIMMER_COUNT;
			if (0 == nTimmerExist[nIndex])
			{
				nTimmerExist[nIndex] = nTimmerCount + 1;
			}
			else
			{
				DeleteTimerQueueTimer(NULL, hTimmer, NULL);
				continue;
			}
		}
		nTimmerCount++;
	} while (nTimmerCount < TIMMER_COUNT);
	
	do 
	{
		QueryPerformanceCounter(&lnStartPerfmcCount);
		
		WaitForSingleObject(g_hEvent, INFINITE);
		
		QueryPerformanceCounter(&lnCurrPerfmcCount);
		
		printf("wait time : %lf milliseconds\n",
			ERASE_TIME_IN_MILLISEC(lnCurrPerfmcCount, lnStartPerfmcCount, lnFrequency));
	} while (TRUE);

#endif

	return 0;
}
九州剑王 2013-01-05
  • 打赏
  • 举报
回复
引用 5 楼 gdstx 的回复:
引用 1 楼 hfz8867879 的回复:内核延时都可以精确到微秒的吧,你可以通过内核的驱动程序去弄计时的地方 具体是怎么搞的?
0.05MS的精度我觉得是有的吧,你把你的sleep的调用改成驱动的通信就好,比如驱动里设置个EVENT,超时50US,然后你的应用程序等待这个EVENT就达到了延时的目的吧
baichi4141 2013-01-05
  • 打赏
  • 举报
回复
sleep的精度不能满足楼主的要求,根源在于通用windows操作系统不能满足楼主的要求 如果楼主用的是嵌入式windows,那我不清楚,但如果是通用的windows操作系统,那么楼主所要求的0.01毫秒的延时精度,我认为是不可能达到的 哪怕楼主所用的线程在不停的循环检测当前时间,随便一个比楼主线程优先级更高的CPU请求,就可以将楼主所用的线程挂起,等到楼主所用的线程重新获得CPU资源,那就不一定是什么时候了 要1ms以上的高精度延时,就不能通过各种浪费资源的通用windows操作系统,必须直接使用硬件资源 当然,如果楼主用的是嵌入式等保证实时性的操作系统,那就当我什么都没说吧
gdstx 2013-01-05
  • 打赏
  • 举报
回复
引用 1 楼 hfz8867879 的回复:
内核延时都可以精确到微秒的吧,你可以通过内核的驱动程序去弄计时的地方
具体是怎么搞的?
gdstx 2013-01-05
  • 打赏
  • 举报
回复
引用 2 楼 VisualEleven 的回复:
本帖最后由 VisualEleven 于 2013-01-05 15:33:42 编辑 C/C++ code ? 123456789101112 LARGE_INTEGER time ; LONGLONG start=0; double dDlay=0; QueryPerformanceCounter(&time) ; ……
这样CPU占用就100%了.
baichi4141 2013-01-05
  • 打赏
  • 举报
回复
windows用IRQL来标示中断优先级,0号中断跟电源相关,级别最高;最低的三级分别是DPC/Dispatch,APC和Passive级别。IA32体系中,优先级最高的中断可以中断优先级低的中断,这叫做中断屏蔽:这也很容易理解:我们生活中总是优先处理更重要的电话。当然,中断屏蔽之前会保存现场,比如你可以在挂掉低级中断的电话前跟对方抱歉,说一会儿再打过去。 中断屏蔽是这样实现的:CPU在接到一个中断请求(IRQ)之后,会提升当前中断请求级别,此时和当前级别相同甚至更低的中断请求就被屏蔽了,所有的时间片都用于当前中断的处理(除了时钟,因为时钟中断级别够高),只有当处理完当前中断后,CPU才会降查看是否有低级中断出现,如果有,就将当前IRQ降低到该中断请求级别并进行处理。所以CPU是否能够及时响应取决于该中断的优先级。那么用户线程(任务)在windows中断级别中排在那个位置呢?——排在最低:Passive级别。这样,用户任务的实时性就无法保证。所以windows不是实时操作系统。
Eleven 2013-01-05
  • 打赏
  • 举报
回复
LARGE_INTEGER time ;
LONGLONG start=0;
double dDlay=0;
QueryPerformanceCounter(&time) ;

start = liTemp.QuadPart ;

while (dDlay- 5.0 < 0.1) // Delay 5ms
{
QueryPerformanceCounter(&time) ;
dDlay= (time.QuadPart - start);
}

大致是这样。。。
九州剑王 2013-01-05
  • 打赏
  • 举报
回复
内核延时都可以精确到微秒的吧,你可以通过内核的驱动程序去弄计时的地方

15,471

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 进程/线程/DLL
社区管理员
  • 进程/线程/DLL社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧