如何在Windows上使线程睡眠时间less于一毫秒
在Windows上,我有一个我从来没有遇到过的问题。 这就是如何让一个线程睡眠不到一毫秒。 在Unix上,通常有多种select(睡眠,睡眠和纳睡)以适应您的需求。 然而,在Windows上,只有毫秒粒度的睡眠 。
在Unix上,我可以使用select
系统调用创build一个微秒级的睡眠,这非常简单:
int usleep(long usec) { struct timeval tv; tv.tv_sec = usec/1000000L; tv.tv_usec = usec%1000000L; return select(0, 0, 0, 0, &tv); }
我怎样才能在Windows上实现相同的?
这表明对睡眠function的误解。 你传递的参数是睡眠的最小时间。 不能保证在指定的时间之后线程会被唤醒。 事实上,线程根本不会“唤醒”,而是被调度程序select执行。 调度程序可能会select等待比请求的睡眠持续时间长得多的时间来激活一个线程,特别是如果另一个线程在那个时刻仍处于活动状态。
正如Joel所说,在这么短的时间内,你不能有意义地“睡觉”(即放弃你预定的CPU)。 如果你想延迟一段时间,那么你需要旋转,反复检查合适的高分辨率定时器(例如“性能计时器”),并希望高优先级的东西不会抢先占领你。
如果你真的关心精确延迟这么短的时间,你不应该使用Windows。
使用winmm.lib中的高分辨率定时器。 看到这个例子。
是的,你需要了解你的操作系统的时间量。 在Windows上,除非将时间段更改为1ms,否则您甚至不会得到1ms的parsing时间。 (使用例如timeBeginPeriod()/ timeEndPeriod())这仍然不能保证什么。 即使是一个小负载或一个蹩脚的设备驱动程序将扔掉一切。
SetThreadPriority()有帮助,但相当危险。 坏的设备驱动程序仍然可以毁了你。
你需要一个超级控制的计算环境来使这个丑陋的东西工作。
如果你需要这么多的粒度,你在错误的地方(在用户空间)。
请记住,如果你在用户空间,你的时间并不总是精确。
调度程序可以启动你的线程(或应用程序),并安排它,所以你依靠操作系统调度程序。
如果你正在寻找精确的东西,你必须去:1)在内核空间(如司机)2)select一个实时操作系统。
无论如何,如果你正在寻找一些粒度(但记住与用户空间的问题)在MSDN中查找QueryPerformanceCounter函数和QueryPerformanceFrequency函数。
正如几个人所指出的,睡眠和其他相关function默认依赖于“系统时钟”。 这是操作系统任务之间的最小时间单位; 例如,调度程序将不会比这更快运行。 即使使用实时操作系统,系统节拍通常也不会less于1毫秒。 虽然它是可调的,但是这对整个系统有影响,而不仅仅是你的睡眠function,因为你的调度程序将更频繁地运行,并且可能增加OS的开销(调度程序运行的时间,任务可以运行的时间)。
解决scheme是使用外部的高速时钟设备。 大多数Unix系统将允许你指定你的定时器和不同的时钟来使用,而不是默认的系统时钟。
你还在等什么需要这样的精确度? 一般来说,如果你需要指定这个精度级别(例如,因为依赖于某些外部硬件),那么你就是在错误的平台上,应该看看实时操作系统。
否则,您应该考虑是否有可以同步的事件,或者最糟糕的情况是只是忙于等待CPU,并使用高性能计数器API来测量已用时间。
通常,hibernate至less持续到下一个系统中断发生。 但是,这取决于多媒体计时器资源的设置。 它可能被设置为接近1毫秒,某些硬件甚至允许以0.9765625的中断周期运行(由NtQueryTimerResolution
提供的实际NtQueryTimerResolution
将显示0.9766,但实际上是错误的,它们不能将正确的数字放入ActualResolution格式。 0.9765625ms,每秒1024中断)。
有一个例外可以让我们摆脱这样一个事实,即不可能在中断时间以内睡觉:这是着名的Sleep(0)
。 这是一个非常强大的工具,它并不经常使用! 它放弃提醒线程的时间片。 这样,线程将停止,直到调度器强制线程再次获得cpu服务。 Sleep(0)
是asynchronous服务,调用将强制调度程序独立于中断而作出反应。
第二种方法是使用waitable object
。 像WaitForSingleObject()
这样的等待函数可以等待一个事件。 为了让线程在任何时候都处于睡眠状态,同样在微秒级别,线程需要设置一些服务线程,这将在所需的延迟时间内生成一个事件。 “睡眠”线程将设置此线程,然后暂停在等待函数,直到服务线程将设置事件信号。
这样任何线程都可以“hibernate”或等待任何时间。 服务线程可能非常复杂,它可能提供系统级的服务,如微秒分辨率的定时事件。 但是,微秒分辨率可能会强制服务线程在最多一个中断周期(〜1ms)内进行高分辨率时间服务。 如果注意,这可以运行得非常好,特别是在多处理器或多核系统上。 对于调用线程和服务线程的亲和性掩码,仔细处理时,在多核系统上,一个ms自旋不会造成太大的伤害。
代码,描述和testing可以在Windows时间戳项目中访问
我有同样的问题,似乎没有什么比ms更快,即使睡眠(0)。 我的问题是客户端和服务器应用程序之间的通信,我使用_InterlockedExchange函数来testing和设置一点,然后我睡觉(0)。
我真的需要以这种方式每秒执行数千次操作,而且不像我计划的那样快。
由于我有一个瘦客户端处理用户,然后调用一个代理,然后说话的线程,我会马上移动合并线程与代理,以便不需要事件接口。
只是为了让大家知道这个Sleep的速度有多慢,我跑了一个10秒钟的testing来完成一个空循环(得到类似于18,000,000个循环的东西),而在这个事件中,我只有180,000个循环。 就是慢100倍!
实际上使用这个usleep函数会导致大的内存/资源泄漏。 (取决于多久调用一次)
使用这个更正的版本(抱歉无法编辑?)
bool usleep(unsigned long usec) { struct timeval tv; fd_set dummy; SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); FD_ZERO(&dummy); FD_SET(s, &dummy); tv.tv_sec = usec / 1000000ul; tv.tv_usec = usec % 1000000ul; bool success = (0 == select(0, 0, 0, &dummy, &tv)); closesocket(s); return success; }
#include <Windows.h> static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution"); static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution"); static void SleepShort(float milliseconds) { static bool once = true; if (once) { ULONG actualResolution; ZwSetTimerResolution(1, true, &actualResolution); once = false; } LARGE_INTEGER interval; interval.QuadPart = -1 * (int)(milliseconds * 10000.0f); NtDelayExecution(false, &interval); }
是的,它使用了一些未公开的内核函数,但是它工作得很好,我使用SleepShort(0.5); 在我的一些threds
就像大家提到的那样,睡眠时间确实没有保证。 但是没有人愿意承认,有时候,在闲置的系统上,usleep命令可以非常精确。 特别是用无滴答的内核。 Windows Vista有它,Linux自2.6.16以来。
无滴答的内核可以帮助改善笔记本电脑的电池寿命:参考英特尔的powertop实用程序。
在这种情况下,我发现Linux usleep命令非常接近地测量了要求的睡眠时间,下降到半个微秒。
所以,也许OP想要一些在空闲系统上大致可以工作的东西,并且能够要求微秒调度! 我其实也想在Windows上。
睡眠(0)听起来像boost :: thread :: yield(),哪个术语更清晰。
我想知道, Boost -timed锁是否有更好的精度。 因为这样你就可以locking一个没有人释放的互斥量,当超时达到时,继续…超时设置为boost :: system_time + boost :: milliseconds&cie(xtime已被弃用)。
尝试boost :: xtime和timed_wait()
具有纳秒级精度。
只要使用睡眠(0)。 0显然小于一毫秒。 这听起来很有趣,但我很认真。 睡眠(0)告诉Windows你现在没有任何事情要做,但是你希望在调度器再次运行时立即重新考虑。 而且由于显然线程不能在调度器本身运行之前被调度,所以这是最短的延迟。
请注意,你可以传递一个微秒的数字给你的睡眠,但是也是这样的:void usleep(__ int64 t){Sleep(t / 1000); } – 没有保证实际睡觉那个时期。
尝试使用SetWaitableTimer …
睡眠function的方式不到一毫秒 – 也许
我发现睡觉(0)为我工作。 在任务pipe理器的cpu负载接近0%的系统上,我编写了一个简单的控制台程序,sleep(0)函数的睡眠时间为1-3微秒,这个时间less于一毫秒。
但从这个线程的上面的答案,我知道睡眠(0)睡眠的数量可以变化比这更大的系统有很大的CPU负载。
但据我所知,睡眠function不应该被用作定时器。 它应该用来使程序尽可能使用尽可能less的CPU的百分比,并尽可能频繁地执行。 对于我的目的来说,比如在一个video游戏中将一个抛物体移动一个像素超过一毫秒,睡眠(0)就可以工作。
你只要确保睡眠间隔时间小于睡眠的最大时间。 你不使用睡眠作为一个计时器,但只是为了使游戏使用的CPU百分比的最低数量可能。 你可以使用一个独立的函数,它没有任何function是通过睡眠来了解什么时候已经过去了一定的时间,然后把这个抛物体在屏幕上移动一个像素 – 例如在1/10毫秒或者100微秒。
伪代码会像这样。
while (timer1 < 100 microseconds) { sleep(0); } if (timer2 >=100 microseconds) { move projectile one pixel } //Rest of code in iteration here
我知道答案可能不适用于高级问题或程序,但可能适用于一些或许多程序。
如果你的目标是“等待很短的时间”,因为你正在做一个自动等待 ,那么你可以执行的等级越来越高。
void SpinOnce(ref Int32 spin) { /* SpinOnce is called each time we need to wait. But the action it takes depends on how many times we've been spinning: 1..12 spins: spin 2..4096 cycles 12..32: call SwitchToThread (allow another thread ready to go on time core to execute) over 32 spins: Sleep(0) (give up the remainder of our timeslice to any other thread ready to run, also allows APC and I/O callbacks) */ spin += 1; if (spin > 32) Sleep(0); //give up the remainder of our timeslice else if (spin > 12) SwitchTothread(); //allow another thread on our CPU to have the remainder of our timeslice else { int loops = (1 << spin); //1..12 ==> 2..4096 while (loops > 0) loops -= 1; } }
所以如果你的目标实际上只是等一下,你可以使用如下的东西:
int spin = 0; while (!TryAcquireLock()) { SpinOne(ref spin); }
这里的优点是我们每次都等得更久,最终完全睡着了。
在Windows上, select
的使用强制你包含Winsock库,它必须在你的应用程序中像这样被初始化:
WORD wVersionRequested = MAKEWORD(1,0); WSADATA wsaData; WSAStartup(wVersionRequested, &wsaData);
然后,select将不允许你没有任何套接字调用,所以你必须做更多的创build一个microsleep方法:
int usleep(long usec) { struct timeval tv; fd_set dummy; SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); FD_ZERO(&dummy); FD_SET(s, &dummy); tv.tv_sec = usec/1000000L; tv.tv_usec = usec%1000000L; return select(0, 0, 0, &dummy, &tv); }
所有这些创build的usleep方法在成功时返回零,并且错误为非零。