it-swarm.cn

如何在Windows上使线程睡眠时间小于一毫秒

在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上实现相同的目标?

51
Jorge Ferreira

这表明对睡眠功能的误解。您传递的参数是 最小 睡眠时间。无法保证线程将在指定的时间后唤醒。实际上,线程根本不会“唤醒”,而是被调度程序选择执行。调度程序可能会选择等待比请求的睡眠持续时间更长的时间来激活线程,尤其是当另一个线程在此时仍处于活动状态时。

88
Joel Coehoorn

正如乔尔所说,你不能在如此短的时间内有意义地“睡觉”(即放弃你的预定CPU)。如果你想延迟很短的时间,那么你需要旋转,反复检查一个适当的高分辨率计时器(例如'性能计时器'),并希望高优先级的东西不会先发制人。

如果你真的关心如此短暂的准确延迟,你不应该使用Windows。

47
Will Dean

使用winmm.lib中提供的高分辨率计时器。请参阅 this 作为示例。

28
Joe Schneider

是的,您需要了解您的操作系统的时间量。在Windows上,除非将时间量更改为1毫秒,否则您甚至不会获得1毫秒的分辨率。 (例如使用timeBeginPeriod()/ timeEndPeriod())这仍然不能保证任何东西。即使是一点负载或一个蹩脚的设备驱动程序也会抛弃一切。

SetThreadPriority()有帮助,但非常危险。糟糕的设备驱动程序仍然会毁了你。

你需要一个超控制的计算环境来让这些丑陋的东西工作。

10
darron
#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

9
Oskar Dahlberg

如果你想要这么多粒度,你就在错误的地方(用户空间)。

请记住,如果您在用户空间,您的时间并不总是精确的。

调度程序可以启动您的线程(或应用程序),并安排它,因此您依赖于OS调度程序。

如果您正在寻找精确的东西,您必须:1)在内核空间(如驱动程序)2)选择一个RTOS。

无论如何,如果您正在寻找一些粒度(但请记住用户空间的问题),请查看MSDN中的QueryPerformanceCounter函数和QueryPerformanceFrequency函数。

6
user16523

正如几位人士指出的那样,睡眠和其他相关功能默认依赖于“系统节拍”。这是OS任务之间的最小时间单位;例如,调度程序的运行速度不会比这更快。即使使用实时操作系统,系统滴答通常也不会小于1毫秒。虽然它是可调的,但这会影响整个系统,而不仅仅是睡眠功能,因为您的调度程序将更频繁地运行,并可能增加操作系统的开销(调度程序运行的时间,相对于任务可以运行的时间)。

解决方案是使用外部高速时钟设备。大多数Unix系统都允许您指定定时器和使用的不同时钟,而不是默认的系统时钟。

5
mbyrne215

通常,睡眠将持续至少直到下一次系统中断发生。但是,这取决于多媒体计时器资源的设置。它可能被设置为接近1 ms的某些东西,某些硬件甚至允许在0.9765625的中断周期运行( ActualResolution NtQueryTimerResolution提供将显示0.9766,但这实际上是错误的。他们只是不能输入正确的数字进入 ActualResolution format。在每秒1024次中断时为0.9765625ms。

有一个例外,它允许我们逃避这样一个事实:它可能无法以低于中断时间的方式睡眠:它是着名的Sleep(0)。这是一个非常强大的工具,并没有经常使用它!它放弃了线程时间片的提醒。这样线程将停止,直到调度程序强制线程再次获得cpu服务。 Sleep(0)是一个异步服务,该调用将强制调度程序独立于中断作出反应。

第二种方法是使用waitable object。像WaitForSingleObject()这样的等待函数可以等待事件。为了使线程在任何时间都处于休眠状态,同时在微秒状态下,线程需要设置一些服务线程,该服务线程将以所需的延迟生成事件。 “休眠”线程将设置此线程,然后在等待函数处暂停,直到服务线程将设置发出信号的事件。

这样任何线程都可以“休眠”或等待任何时间。服务线程可能很复杂,它可能提供系统范围的服务,如微秒分辨率的定时事件。但是,微秒分辨率可能会迫使服务线程在高分辨率时间服务上旋转最多一个中断周期(~1ms)。如果小心,这可以很好地运行,特别是在多处理器或多核系统上。当仔细处理调用线程和服务线程的关联掩码时,一个ms的自旋对多核系统没有太大影响。

可以在 Windows时间戳项目中访问代码,描述和测试

4
Arno

你还等什么需要这么精确?通常,如果您 需要 指定该精度级别(例如,由于对某些外部硬件的依赖性),您就是在错误的平台上并且应该查看实时操作系统。

否则,您应该考虑是否有可以同步的事件,或者在更糟糕的情况下,只是忙着等待CPU并使用高性能计数器API来测量经过的时间。

4
Rob Walker

实际上使用这个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;
}
2
Hendrik

我有同样的问题,似乎没有什么比ms更快,甚至睡眠(0)。我的问题是客户端和服务器应用程序之间的通信,我使用_InterlockedExchange函数来测试和设置一个位然后我睡眠(0)。

我真的需要以这种方式每秒执行数千次操作,并且它的工作速度不如我计划的那么快。

由于我有一个瘦客户端处理用户,后者又调用一个代理然后与一个线程进行对话,我将尽快将线程与代理合并,这样就不需要事件接口。

只是为了让你们知道这个Sleep的速度有多慢,我跑了一个测试10秒,执行一个空循环(获得类似18,000,000个循环),而事件到位时我只有180,000个循环。也就是说,慢了100倍!

2
Celso Bressan

像所有提到的人一样,确实没有关于睡眠时间的保证。但是没有人愿意承认,有时候,在空闲系统上,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&cie(不推荐使用xtime)。

1
Lightness1024

尝试使用 SetWaitableTimer ...

1
andrewrk

尝试boost :: xtime和timed_wait()

具有纳秒精度。

0
theschmitzer

只需使用Sleep(0)。 0显然小于一毫秒。现在,这听起来很有趣,但我很认真。 Sleep(0)告诉Windows您现在没有任何操作,但是您希望在调度程序再次运行时立即重新考虑。而且很明显,线程无法在调度程序本身运行之前调度运行,这是可能的最短延迟。

请注意,您可以将微秒数传递给usleep,但是usleep(__ int64 t){Sleep(t/1000); - 无法保证实际睡觉那段时间。

0
MSalters

如果你的目标是 “等待很短的时间” 因为你正在做 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);
}

这里的优点是我们每次等待更长时间,最终完全沉睡。

0
Ian Boyd

睡眠功能小于一毫秒 - 也许

我发现睡眠(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

我知道答案可能不适用于高级问题或程序,但可能适用于某些或许多程序。

0
rauprog