首页 / 知识

关于C ++:如何在Windows上使线程休眠不到一毫秒

2023-04-16 16:50:00

关于C ++:如何在Windows上使线程休眠不到一毫秒

How to make thread sleep less than a millisecond on Windows

在Windows上,我有一个在Unix上从未遇到过的问题。 这就是使线程休眠不到一毫秒的方法。 在Unix上,通常有多种选择(睡眠,usleep和nanosleep)可以满足您的需求。 但是,在Windows上,只有"睡眠"级别为毫秒。

在Unix上,我可以使用select系统调用来创建一个微秒的睡眠,这非常简单:

1
2
3
4
5
6
7
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上实现相同的目标?


这表明对睡眠功能有误解。您传递的参数是最短睡眠时间。不能保证线程会在指定的时间之后立即唤醒。实际上,线程根本不会"唤醒",而是由调度程序选择执行。调度程序可能选择等待比请求的睡眠时间更长的时间来激活一个线程,特别是如果此时另一个线程仍处于活动状态。


正如Joel所说,您不能在这么短的时间内有意义地"睡眠"(即放弃您计划的CPU)。如果您想延迟一小段时间,则需要旋转,反复检查一个合适的高分辨率计时器(例如``性能计时器''),并希望高优先级的东西仍然不会抢先。

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


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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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);在我的一些烦恼中


是的,您需要了解操作系统的时间范围。在Windows上,除非将时间段更改为1ms,否则您甚至都不会获得1ms的分辨率时间。 (例如使用timeBeginPeriod()/ timeEndPeriod())实际上仍然不能保证任何东西。即使是很小的负载或单个糟糕的设备驱动程序,都将使所有东西丢掉。

SetThreadPriority()有帮助,但是非常危险。不良的设备驱动程序仍然可能毁了您。

您需要一个超受控的计算环境才能使这些难看的东西完全起作用。


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

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

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

如果您正在寻找精确的东西,则必须去:
1)在内核空间(如驱动程序)
2)选择一个实时操作系统。

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


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

有一个例外可以让我们摆脱以下事实:睡眠时间少于中断时间是不可能的:这是著名的Sleep(0)。这是非常强大的
工具,并没有经常使用!它放弃了线程时间片的提醒。这样,线程将停止,直到调度程序迫使该线程再次获得cpu服务为止。 Sleep(0)是异步服务,该调用将强制调度程序独立于中断做出反应。

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

这样,任何线程都可以"休眠"或等待任何时间。服务线程可能非常复杂,并且可能提供系统范围的服务,例如以微秒为单位的定时事件。但是,微秒的分辨率可能会迫使服务线程在高分辨率时间服务上旋转最多一个中断周期(?1ms)。如果注意,可以
运行得非常好,尤其是在多处理器或多核系统上。如果仔细处理了调用线程和服务线程的相似性掩码,则在多核系统上旋转1 ms不会造成很大的伤害。

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


正如一些人指出的那样,睡眠和其他相关功能默认情况下取决于"系统刻度"。这是OS任务之间的最小时间单位;例如,调度程序的运行速度不会超过此速度。即使使用实时操作系统,系统滴答通常也不小于1毫秒。尽管它是可调的,但这不仅影响睡眠功能,还影响整个系统,因为调度程序将更频繁地运行,并有可能增加操作系统的开销(调度程序运行的时间,而不是运行时间)。任务可以运行的时间)。

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


您还在等什么需要这种精度的东西?通常,如果您需要指定该精度级别(例如,由于对某些外部硬件的依赖性),那么您使用的平台不正确,应该查看实时操作系统。

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


实际上,使用此usleep函数将导致大量内存/资源泄漏。 (取决于调用频率)

使用此更正的版本(抱歉,无法编辑?)

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

我有同样的问题,似乎没有什么比ms更快的了,即使Sleep(0)也是如此。我的问题是客户端和服务器应用程序之间的通信,在此我使用_InterlockedExchange函数测试并设置一个位,然后我进入Sleep(0)。

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

由于我有一个与用户打交道的瘦客户端,后者又调用了一个代理,该代理然后与线程进行对话,因此我将尽快将线程与代理合并,从而不需要事件接口。

只是为了让大家知道这个睡眠有多慢,我进行了一个10秒钟的测试,执行一个空循环(得到18,000,000个循环),而有了该事件,我只有180,000个循环。也就是说,慢100倍!


尝试使用SetWaitableTimer ...


如果您的目标是因为正在进行自旋等待而"等待很短的时间",那么可以执行的等待级别就会增加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
   }
}

因此,如果您的目标实际上是只等待一小会儿,则可以使用以下方法:

1
2
3
4
5
int spin = 0;
while (!TryAcquireLock())
{
   SpinOne(ref spin);
}

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


就像每个人提到的那样,确实不能保证睡眠时间。
但是没有人愿意承认有时在空闲系统上,usleep命令可能非常精确。尤其是带有无滴答的内核。从2.6.16开始,WindowsVista拥有它,而Linux拥有它。

存在不滴答的内核以帮助改善笔记本电脑的电池续航时间:c.f。英特尔的powertop实用程序。

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

因此,OP可能希望在空闲系统上大部分时间都能正常工作,并且能够要求微秒的调度!
我实际上也希望在Windows上使用。

Sleep(0)听起来也像boost :: thread :: yield(),这是更清晰的术语。

我不知道Boost定时锁是否具有更好的精度。因为这样您就可以锁定没有人释放的互斥量,并且当超时时,继续...
超时设置为boost :: system_time + boost :: milliseconds&cie(不建议使用xtime)。


尝试boost :: xtime和一个timed_wait()

具有纳秒级精度。


睡眠功能可能不到一毫秒

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

但是从该线程的上述答案中,我知道在具有大量cpu负载的系统上,sleep(0)睡眠的数量比该数量的变化要大得多。

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

您只需要确保睡眠间隔比最大睡眠时间小即可。您不使用睡眠作为计时器,而只是为了使游戏尽可能使用最少的CPU百分比。您将使用一个与睡眠无关的独立函数,以了解何时经过了特定的时间,然后将弹丸在屏幕上移动一个像素(时间为1/10毫秒或100微秒) 。

伪代码会像这样。

1
2
3
4
5
6
7
8
9
while (timer1 < 100 microseconds) {
sleep(0);
}

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

//Rest of code in iteration here

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


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

请注意,您可以将微秒数传递给您的usleep,但是无效的usleep(__ int64 t){Sleep(t / 1000); }-不能保证实际睡那个时期。


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

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

然后,select将不允许您在没有任何套接字的情况下被调用,因此您必须做更多的事情才能创建微睡眠方法:

1
2
3
4
5
6
7
8
9
10
11
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方法成功都将返回零,而对于错误则返回非零。


线程休眠方法选择

最新内容

相关内容

猜你喜欢