在Windows上我遇到了一个我在Unix上从未遇到过的问题。这是如何使线程休眠不到一毫秒。在Unix上,您通常有许多选择(睡眠,睡眠和纳米睡眠)以满足您的需求。但是,在Windows上,只有 Sleep 具有毫秒级的粒度。
在Unix上,我可以使用select
系统调用来创建一个非常简单的微秒睡眠:
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上实现相同的目标?
答案 0 :(得分:88)
这表明对睡眠功能的误解。您传递的参数是睡眠的最小时间。无法保证线程将在指定的时间后唤醒。实际上,线程根本不会“唤醒”,而是被调度程序选择执行。调度程序可能会选择等待比请求的睡眠持续时间更长的时间来激活线程,尤其是当另一个线程在此时仍处于活动状态时。
答案 1 :(得分:47)
正如乔尔所说,你不能在如此短的时间内有意义地“睡觉”(即放弃你的预定CPU)。如果你想延迟一段时间,那么你需要旋转,反复检查一个合适的高分辨率计时器(例如'性能计时器'),并希望高优先级的东西不会先发制人。
如果你真的关心这么短的时间的准确延迟,你就不应该使用Windows。
答案 2 :(得分:28)
使用winmm.lib中提供的高分辨率计时器。有关示例,请参阅this。
答案 3 :(得分:10)
是的,您需要了解您的操作系统的时间量。在Windows上,除非将时间量更改为1毫秒,否则您甚至不会获得1毫秒的分辨率。 (例如使用timeBeginPeriod()/ timeEndPeriod())这仍然不能保证任何东西。即使是一点点负载或一个蹩脚的设备驱动程序也会抛弃所有东西。
SetThreadPriority()有帮助,但非常危险。糟糕的设备驱动程序仍然会毁了你。
你需要一个超级控制的计算环境来让这些丑陋的东西发挥作用。
答案 4 :(得分:6)
如果您想要这么多粒度,那么您就在错误的位置(在用户空间中)。
请记住,如果您在用户空间,那么您的时间并不总是精确的。
调度程序可以启动您的线程(或应用程序)并安排它,因此您依赖于OS调度程序。
如果您正在寻找精确的东西,您必须去: 1)在内核空间(如驱动程序) 2)选择RTOS。
无论如何,如果你正在寻找一些粒度(但记住用户空间的问题)看 MSDN中的QueryPerformanceCounter函数和QueryPerformanceFrequency函数。
答案 5 :(得分:5)
正如几位人士指出的那样,睡眠和其他相关功能默认依赖于“系统节拍”。这是OS任务之间的最小时间单位;例如,调度程序的运行速度不会比这更快。即使使用实时操作系统,系统滴答通常也不会小于1毫秒。虽然它是可调的,但这会影响整个系统,而不仅仅是睡眠功能,因为您的调度程序将更频繁地运行,并可能增加操作系统的开销(调度程序运行的时间,相对于任务可以运行的时间)。
解决方案是使用外部高速时钟设备。大多数Unix系统都允许您指定计时器和不同的时钟,而不是默认的系统时钟。
答案 6 :(得分:4)
你还等什么需要这么精确?一般情况下,如果您需要指定该级别的精度(例如,由于对某些外部硬件的依赖性),您就是在错误的平台上并且应该查看实时操作系统。
否则你应该考虑是否有可以同步的事件,或者更糟糕的是,只是忙着等待CPU并使用高性能计数器API来测量经过的时间。
答案 7 :(得分:4)
通常,睡眠将持续至少直到下一次系统中断发生。但是,这个
取决于多媒体计时器资源的设置。它可能被设置为接近的东西
1 ms,某些硬件甚至允许以0.9765625的中断周期运行(NtQueryTimerResolution
提供的 ActualResolution 将显示0.9766,但这实际上是错误的。他们无法将正确的数字放入< em> ActualResolution 格式。在每秒1024次中断时为0.9765625ms。
有一个例外,它允许我们摆脱这样一个事实,即它可能无法在中断时间内睡眠:这是着名的Sleep(0)
。这是一个非常强大的
工具并没有经常使用它!它放弃了线程时间片的提醒。这样线程将停止,直到调度程序强制线程再次获得cpu服务。 Sleep(0)
是一个异步服务,该调用将强制调度程序独立于中断做出反应。
第二种方法是使用waitable object
。像WaitForSingleObject()
这样的等待函数可以等待事件。为了使线程在任何时间都处于休眠状态,同时在微秒状态下,线程需要设置一些服务线程,该服务线程将以所需的延迟生成事件。 “休眠”线程将设置此线程,然后在等待函数处暂停,直到服务线程将设置发出信号的事件。
这样任何线程都可以“休眠”或等待任何时间。服务线程可能很复杂,它可能提供系统范围的服务,如微秒分辨率的定时事件。但是,微秒分辨率可能会迫使服务线程在高分辨率时间服务上旋转最多一个中断周期(~1ms)。如果小心,这可以 运行得非常好,尤其适用于多处理器或多核系统。当仔细处理调用线程和服务线程的关联掩码时,一个ms的自旋对多核系统没有太大影响。
访问代码,说明和测试答案 8 :(得分:2)
我有同样的问题,似乎没有什么比ms更快,甚至睡眠(0)。我的问题是客户端和服务器应用程序之间的通信,我使用_InterlockedExchange函数来测试和设置一个位然后我睡眠(0)。
我真的需要以这种方式每秒执行数千次操作,并且它的工作速度不如我计划的那么快。
由于我有一个瘦客户端处理用户,而后者又调用一个代理然后与一个线程进行对话,我将尽快将线程与代理合并,这样就不需要事件接口。
只是为了让大家知道这个睡眠有多慢,我跑了一个测试10秒钟执行一个空循环(获得18,000,000个循环),而事件到位我只有180,000个循环。也就是说,慢了100倍!
答案 9 :(得分:2)
实际上使用这个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;
}
答案 10 :(得分:1)
与所有人提到的一样,确实无法保证睡眠时间。 但是没有人愿意承认,有时候,在空闲系统上,usleep命令可以非常精确。特别是使用无滴答内核。 Windows Vista拥有它,Linux从2.6.16开始就拥有它。
Tickless内核的存在有助于改善笔记本电脑的生活:c.f。英特尔的powertop实用程序。
在这种情况下,我发现测量了Linux usleep命令,该命令非常接近地尊重所请求的睡眠时间,低至半秒微秒。
所以,也许OP想要的东西大部分时间都会在空转系统中大致工作,并且能够要求微秒级调度! 我实际上也希望在Windows上使用它。
Sleep(0)听起来像boost :: thread :: yield(),这个术语更清晰。
我想知道Boost - 定时锁是否具有更好的精度。因为那时你可以锁定一个没有人发布的互斥锁,当达到超时时,继续... 超时设置为boost :: system_time + boost :: milliseconds&amp; cie(xtime已弃用)。
答案 11 :(得分:1)
尝试使用SetWaitableTimer ...
答案 12 :(得分:0)
尝试使用boost :: xtime和timed_wait()
具有纳秒精度。
答案 13 :(得分:0)
只需使用Sleep(0)即可。 0显然小于一毫秒。现在,这听起来很有趣,但我很认真。 Sleep(0)告诉Windows您现在没有任何操作,但是您希望在调度程序再次运行时立即重新考虑。而且很明显,线程无法在调度程序本身运行之前调度运行,这是可能的最短延迟。
请注意,你可以将微秒数传递给你的睡眠,但是usleep也是如此(__ int64 t){Sleep(t / 1000); - 无法保证实际睡觉那个时期。
答案 14 :(得分:0)
睡眠功能小于毫秒 - 也许
我发现睡眠(0)对我有效。在任务管理器中cpu上负载接近0%的系统上,我写了一个简单的控制台程序,sleep(0)函数睡眠时间为1-3微秒,这个时间不到一毫秒。
但是从这个帖子中的上述答案来看,我知道睡眠(0)睡眠的数量可能比具有大cpu负载的系统上的情况要大得多。
但据我了解,睡眠功能不应该用作计时器。它应该用于使程序尽可能使用最小百分比的cpu并尽可能频繁地执行。为了我的目的,比如在一个视频游戏中将一个射弹穿过屏幕的速度远远快于一个像素一毫秒,我认为睡眠(0)可以工作。
您只需确保睡眠间隔小于它睡眠的最长时间。您不使用睡眠作为计时器,只是为了让游戏尽可能使用最小的CPU百分比。你可以使用一个单独的函数来做睡眠,以便在特定时间过去后知道,然后在一个十分之一毫秒或100微秒的时间内将射弹移动一个像素穿过屏幕。
伪代码会像这样。
while (timer1 < 100 microseconds) {
sleep(0);
}
if (timer2 >=100 microseconds) {
move projectile one pixel
}
//Rest of code in iteration here
我知道答案可能不适用于高级问题或程序,但可能适用于某些或许多程序。
答案 15 :(得分:0)
如果你的目标是&#34;等待很短的时间&#34; ,因为你正在做 spinwait ,那么等你可以表演。
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);
}
这里的优点是我们每次都要等待更长时间,最终完全沉睡。
答案 16 :(得分:-2)
在Windows上,使用select
会强制您包含必须在您的应用程序中初始化的Winsock库:
WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
然后select不允许你在没有任何套接字的情况下被调用,所以你需要做更多的事情来创建一个微睡眠方法:
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);
}
所有这些创建的usleep方法在成功时返回零,对于错误则返回非零。