61

在 Windows 上,我有一个在 Unix 上从未遇到过的问题。这就是如何让线程休眠不到一毫秒。在 Unix 上,您通常有多种选择(sleep、usleep 和 nanosleep)来满足您的需求。然而,在 Windows 上,只有毫秒粒度的睡眠。

在 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 上达到同样的效果?

4

18 回答 18

88

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

于 2008-09-17T16:40:21.543 回答
48

正如 Joel 所说,您不能在如此短的时间内有意义地“睡眠”(即放弃您预定的 CPU)。如果您想延迟一小段时间,那么您需要旋转,反复检查适当的高分辨率计时器(例如“性能计时器”),并希望高优先级的东西无论如何都不会抢占您。

如果您真的关心如此短时间的准确延迟,那么您不应该使用 Windows。

于 2008-09-17T16:45:57.973 回答
34

使用 winmm.lib 中提供的高分辨率多媒体计时器。请参阅示例。

于 2008-09-17T16:46:01.593 回答
20
#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);
}

非常适合在极短的时间内睡觉。请记住,在某一点上,实际延迟永远不会是一致的,因为系统无法在如此短的时间内保持一致的延迟。

于 2015-07-14T15:57:51.650 回答
10

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

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

你需要一个超可控的计算环境才能让这些丑陋的东西完全发挥作用。

于 2008-09-17T16:49:19.743 回答
7

通常睡眠至少会持续到下一个系统中断发生。但是,这取决于多媒体定时器资源的设置。它可能设置为接近 1 ms,某些硬件甚至允许以 0.9765625 的中断周期运行(由提供的ActualResolutionNtQueryTimerResolution将显示 0.9766 但这实际上是错误的。他们只是无法将正确的数字放入ActualResolution格式中。它是 0.9765625毫秒,每秒 1024 次中断)。

有一个例外可以让我们摆脱这样一个事实,即在中断期间内可能无法入睡:它是著名的Sleep(0). 这是一个非常强大的工具,它没有像应有的那样经常使用!它放弃了线程时间片的提醒。这样线程将停止,直到调度程序强制线程再次获得 cpu 服务。Sleep(0)是一个异步服务,调用将强制调度程序独立于中断作出反应。

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

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

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

于 2012-07-12T16:12:46.743 回答
6

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

解决方案是使用外部高速时钟设备。大多数 Unix 系统允许你指定你的计时器和这样一个不同的时钟来使用,而不是默认的系统时钟。

于 2008-09-17T18:03:10.953 回答
5

您还在等什么,需要如此精确?一般来说,如果您需要指定该级别的精度(例如,由于对某些外部硬件的依赖),您就在错误的平台上,应该查看实时操作系统。

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

于 2008-09-17T16:47:49.450 回答
5

如果您想要如此多的粒度,那么您来错地方了(在用户空间中)。

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

调度程序可以启动您的线程(或应用程序)并对其进行调度,因此您依赖于操作系统调度程序。

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

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

于 2008-09-17T18:03:35.290 回答
3

实际上使用这个 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;
}
于 2009-09-19T16:34:33.450 回答
2

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

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

由于我有一个与用户打交道的瘦客户端,它反过来调用一个代理,然后与一个线程对话,我将很快将线程与代理合并,这样就不需要事件接口。

只是为了让你们知道这个 Sleep 有多慢,我运行了一个 10 秒的测试,执行一个空循环(得到大约 18,000,000 个循环),而在事件发生后我只有 180,000 个循环。也就是说,慢了 100 倍!

于 2009-05-13T20:02:20.400 回答
2

尝试使用SetWaitableTimer ...

于 2015-07-03T06:49:20.647 回答
1

就像大家提到的那样,睡眠时间确实无法保证。但没有人愿意承认,有时在空闲系统上,usleep 命令可能非常精确。特别是使用无滴答内核。Windows Vista 拥有它,Linux 从 2.6.16 开始拥有它。

Tickless 内核的存在有助于提高笔记本电脑的电池寿命:参见英特尔的 powertop 实用程序。

在那种情况下,我碰巧测量了 Linux usleep 命令,它非常接近请求的睡眠时间,低至半打微秒。

所以,也许 OP 想要一些在空闲系统上大部分时间都能大致工作的东西,并且能够要求微秒调度!我实际上也希望在 Windows 上使用它。

另外,Sleep(0) 听起来像 boost::thread::yield(),术语更清楚。

我想知道Boost定时锁是否有更好的精度。因为那时你可以锁定一个没有人释放的互斥锁,当达到超时时,继续...超时设置为 boost::system_time + boost::milliseconds & cie(不推荐使用 xtime)。

于 2012-05-11T15:50:02.383 回答
1

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

这里的优点是我们每次都等待更长的时间,最终完全进入睡眠状态。

于 2017-04-22T21:18:41.500 回答
0

尝试 boost::xtime 和 timed_wait()

具有纳秒精度。

于 2008-09-17T17:00:44.410 回答
0

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

请注意,您可以将微秒数传递给您的睡眠,但 void usleep(__int64 t) { Sleep(t/1000); } - 不能保证那段时间真的睡着了。

于 2008-09-18T08:59:28.600 回答
0

不到一毫秒的睡眠功能-也许

我发现 sleep(0) 对我有用。在任务管理器中 cpu 负载接近 0% 的系统上,我编写了一个简单的控制台程序,并且 sleep(0) 函数的睡眠时间一致为 1-3 微秒,不到一毫秒。

但是从这个线程的上述答案中,我知道 sleep(0) 睡眠量在 CPU 负载较大的系统上的变化可能比这大得多。

但据我了解,睡眠功能不应该用作计时器。它应该用于使程序使用尽可能少的 cpu 百分比并尽可能频繁地执行。就我的目的而言,例如在视频游戏中以比每毫秒一个像素快得多的速度在屏幕上移动弹丸,我认为 sleep(0) 有效。

您只需确保睡眠间隔小于最大睡眠时间。您不将睡眠用作计时器,而只是为了使游戏使用尽可能少的 cpu 百分比。您将使用一个与睡眠无关的单独功能,以了解何时过去了特定的时间,然后在屏幕上移动一个像素——例如 1/10 毫秒或 100 微秒.

伪代码会是这样的。

while (timer1 < 100 microseconds) {
sleep(0);
}

if (timer2 >=100 microseconds) {
move projectile one pixel
}

//Rest of code in iteration here

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

于 2015-10-31T05:33:02.700 回答
-4

在 Windows 上,使用select强制您包含Winsock库,该库必须像这样在您的应用程序中初始化:

WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);

然后 select 不允许你在没有任何套接字的情况下被调用,所以你必须做更多的事情来创建一个 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);
}

所有这些创建的 usleep 方法在成功时返回零,在错误时返回非零。

于 2008-09-17T16:39:51.980 回答