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

有帮助吗?

解决方案 18

在 Windows 上使用 select 强制你包括 温索克 库必须在您的应用程序中像这样初始化:

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 方法在成功时返回零,在错误时返回非零。

其他提示

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

正如乔尔所说,你无法有意义地“睡觉”(即在如此短的时间内放弃您的预定CPU)。如果你想延迟一小段时间,那么你需要旋转,反复检查合适的高分辨率计时器(例如“性能计时器”)并希望高优先级的事情不会抢占您的注意力。

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

使用 winmm.lib 中提供的高分辨率计时器。看 举个例子。

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

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

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

#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);在我的一些线程中

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

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

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

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

无论如何,如果您正在寻找某种粒度(但请记住用户空间的问题),请寻找MSDN中查询Perforformancecounter函数和查询性能函数。

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

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

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

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

一般来说,睡眠至少会持续到下一个系统中断发生为止。但是,这取决于多媒体计时器资源的设置。它可以设置为接近1 ms的东西,一些硬件甚至允许在0.9765625的中断期间运行(实际分辨率 由...提供 NtQueryTimerResolution 将显示 0.9766 但这实际上是错误的。他们只是无法将正确的数字输入 实际分辨率 格式。每秒 1024 个中断时为 0.9765625ms)。

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

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

这样任何线程都可以“睡眠”或等待任何时间。服务线程可能非常复杂,并且它可以提供系统范围的服务,例如微秒分辨率的定时事件。然而,微秒分辨率可能会迫使服务线程在高分辨率时间服务上旋转最多一个中断周期(~1ms)。如果要注意,这可以很好地运行,特别是在多处理器或多核系统上。当调用线程和服务线程的亲和力掩码得到仔细处理时,一毫秒的自旋不会对多核系统产生太大影响。

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

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

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

由于我有一个与用户打交道的瘦客户端,该客户端又调用一个代理,然后该代理与线程通信,因此我将很快将线程与代理合并,以便不需要事件接口。

为了让大家了解这个 Sleep 有多慢,我运行了 10 秒的测试,执行一个空循环(得到大约 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;
}

正如大家提到的,睡眠时间确实无法保证。但没有人愿意承认,有时,在空闲系统上,usleep 命令可以非常精确。特别是对于无滴答内核。Windows Vista 具有该功能,Linux 自 2.6.16 起也具有该功能。

Tickless 内核的存在可以帮助延长笔记本电脑的电池寿命:参见英特尔的 powertop 实用程序。

在这种情况下,我碰巧测量了 Linux usleep 命令,该命令非常严格地遵守所请求的睡眠时间,低至六微秒。

因此,也许 OP 想要一些在空闲系统上大部分时间都能正常工作的东西,并且能够请求微秒调度!实际上我也希望在 Windows 上也能实现这一点。

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

我怀疑是否 促进- 定时锁具有更好的精度。因为这样你就可以锁定一个没有人释放的互斥体,并且当达到超时时,继续......超时设置为 boost::system_time + boost::milliseconds & cie (xtime 已弃用)。

尝试使用 设置等待定时器...

尝试 boost::xtime 和 timed_wait()

具有纳秒级精度。

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

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

睡眠功能可能少于一毫秒

我发现 sleep(0) 对我有用。在任务管理器中 cpu 负载接近 0% 的系统上,我编写了一个简单的控制台程序,并且 sleep(0) 函数持续休眠 1-3 微秒,这远小于一毫秒。

但从本线程的上述答案中,我知道 sleep(0) 睡眠的数量可能比 CPU 负载较大的系统上的变化更大。

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

您只需确保睡眠间隔远小于其最大睡眠时间即可。您不使用睡眠作为计时器,而只是为了让游戏使用尽可能少的 CPU 百分比。您可以使用一个与 sleep 无关的单独函数来了解特定时间的流逝时间,然后将射弹在屏幕上移动一个像素,时间为 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);
}

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top