【问题标题】:Thread.Sleep(1) takes longer than 1ms [duplicate]Thread.Sleep(1) 耗时超过 1ms [重复]
【发布时间】:2013-10-04 16:53:18
【问题描述】:

我搜索了这个问题,但没有看到答案。如果它是重复的,我很乐意将其关闭。

我目前正在尝试对一项技术进行一些性能评估,并看到了一些令人难以置信的结果,因此我决定进行一些实验。为此,我想尝试看看 Stopwatch 类是否返回了我的预期。

Stopwatch sw = Stopwatch.StartNew();
Thread.Sleep(1);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

在这种情况下,我几乎看到了 15 毫秒的返回值。我知道 DateTime 的分辨率在那里,但 Thread.Sleep(1) 不应该让线程休眠 1ms 吗?我所在的系统返回 Stopwatch.IsHighResolution true 并在 .NET 4 中运行。

背景: 这段完整和正确形式的代码旨在收集一些关于 Aerospike db get 请求的数字。数据库不在同一个盒子上。当我在查询中间打印出 sw.ElapsedMilliseconds 时,我看到的主要是亚毫秒级响应,考虑到我的 Java 等效代码大部分时间返回更可信的 5ms-15ms 响应,这听起来有点可疑。 Java 代码使用 System.nanoTime() 的差异。我的 C# 代码中的 submilli 响应是指 Console.WriteLine(sw.ElapsedMilliseconds) 打印 0。

【问题讨论】:

  • 我认为对于非常低的数量,线程进入睡眠并再次醒来需要更多的实际时间。我建议你尝试增加 sleep() 参数,看看参数和实际睡眠时间的数量是相等的。
  • 我将其逐步提高到大约 100 毫秒,但我仍然始终看到高出 5 到 15 毫秒。我知道它不是实时操作系统,但我没想到会看到这个
  • 我不会说它一定是重复的,但答案就在那里。我应该删除这个问题吗?

标签: c# .net


【解决方案1】:

秒表以外的其他定时器由时钟中断递增。默认情况下,在 Windows 上每秒滴答 64 次。或 15.625 毫秒。所以一个小于 16 的 Thread.Sleep() 参数不会给你你正在寻找的延迟,你总是会得到至少 15.625 的间隔。同样,如果您读取 Environment.TickCount 或 DateTime.Now 并等待 少于 16 毫秒,那么您将读取相同的值并认为 0 毫秒已经过去。

始终使用秒表进行小增量测量,它使用不同的频率源。它的分辨率是可变的,它取决于主板上的芯片组。但是你可以相信它比微秒更好。 Stopwatch.Frequency 为您提供速率。

时钟中断率可以改变,你必须调用 timeBeginPeriod()。这可以让你降低到一毫秒,实际上使 Thread.Sleep(1) 准确。最好不要这样做,对电源很不友好。

【讨论】:

  • 好吧,总是有多媒体计时器
  • 对于其他来到这个线程的人,我在 linux/ubuntu 下使用 Thread.Sleep(1) 将线程睡眠时间精确为 1 毫秒。
  • @JaredBroad -- 当然,问题是特定于 Windows 的。 (答案为您提供了有关如何“修复”Windows 中的行为的线索——但同样,它很耗电,因此,对于大多数情况,不值得。)
  • 这对我很有帮助。看到一个简单的问题,在我的笔记本电脑上 Thread.Sleep 的 1ms 大约是 1-2ms。在我的 Azure VM 上执行完全相同的代码给了我 15-16 毫秒。很奇怪,但现在我们知道为什么了!
【解决方案2】:

我希望 Thread.Sleep 只不过是 Win32 Sleep API 的托管包装器。众所周知,这个 API 函数的分辨率很低。文档是这样说的:

此函数使线程放弃其时间片的剩余部分,并在基于 dwMilliseconds 值的间隔内变得不可运行。系统时钟以恒定速率“滴答”。如果 dwMilliseconds 小于系统时钟的分辨率,则线程可能休眠的时间少于指定的时间长度。如果 dwMilliseconds 大于 1 个滴答但小于 2 个滴答,则等待可以介于 1 到 2 个滴答之间,依此类推。要提高睡眠间隔的准确性,请调用 timeGetDevCaps 函数以确定支持的最小计时器分辨率,并调用 timeBeginPeriod 函数将计时器分辨率设置为最小值。调用 timeBeginPeriod 时要小心,因为频繁调用会显着影响系统时钟、系统电源使用和调度程序。如果您调用 timeBeginPeriod,请在应用程序的早期调用一次,并确保在应用程序的最后调用 timeEndPeriod 函数。

经过休眠间隔后,线程准备好运行。如果您指定 0 毫秒,线程将放弃其时间片的剩余部分,但仍保持就绪状态。请注意,不保证就绪线程会立即运行。因此,线程可能要等到休眠间隔过去一段时间后才会运行。有关详细信息,请参阅调度优先级。

因此,您可以按照建议使用timeBeginPeriodtimeEndPeriod 来提高计时器分辨率,但实际上不建议这样做。如果您确实需要在短时间内阻止,那么我建议您找到更好的解决方案。例如多媒体计时器。

【讨论】:

    【解决方案3】:

    是的,你的想法是绝对正确的。

    Thread.Sleep 不保证在N 毫秒内释放。它将确保Sleep 不会在N 毫秒之前释放。

    换句话说,Thread.Sleep(1) 意味着休眠至少 1 毫秒。操作系统不会为特定线程安排特定时间段的时间片。

    操作系统不会在指定的时间内安排线程执行。此方法将线程的状态更改为包含 WaitSleepJoin。

    来自Msdn

    【讨论】:

      【解决方案4】:

      搜索“线程睡眠解决方案”会出现这些相关的 SO 问题:

      共识是,默认情况下,Sleep() 至少需要 15 毫秒,除非您使用 timeBegin/EndPeriod() 设置分辨率,如 answer

      【讨论】:

        【解决方案5】:

        Thread.Sleep(n) 意味着阻塞当前线程至少在 n 毫秒内可能发生的时间片(或线程量子)数量。时间片的长度在不同版本/类型的 Windows 和不同的处理器上是不同的,一般在 15 到 30 毫秒之间。这意味着线程几乎可以保证阻塞超过 n 毫秒。您的线程将在 n 毫秒后准确地重新唤醒的可能性几乎是不可能的。所以,Thread.Sleep 对计时毫无意义。

        【讨论】:

        • 我实际上并没有使用 Thread.Sleep 进行计时,而是为了验证我极快的响应时间,事实证明,这似乎是准确的。不过我绝对理解你的意思。
        【解决方案6】:

        Thread.Sleep(1) 会让线程休眠 1 毫秒,但您通常无法获得该精度。

        但是,一旦线程进入睡眠状态,内核需要时间将其放回就绪队列,然后在线程真正再次运行时将其放入运行状态。

        所有这些因素都取决于平台、CPU 的数量、其他进程的数量等。对于非 RT 操作系统,您无法得到任何保证。

        【讨论】:

        • 基于 RT 的设备是否在此处提供不同的合同?这是相关的,因为我正在开发一个单独的 RT 兼容应用程序。
        猜你喜欢
        • 1970-01-01
        • 2020-11-17
        • 1970-01-01
        • 1970-01-01
        • 2013-10-22
        • 1970-01-01
        • 2010-10-07
        • 1970-01-01
        相关资源
        最近更新 更多