【问题标题】:SwitchToThread vs Sleep(1)SwitchToThread 与睡眠(1)
【发布时间】:2010-11-25 22:01:57
【问题描述】:

我想知道调用 Thread.Sleep(1) 和调用 SwitchToThread 之间的实际区别是什么(如果我们忽略它当前没有被 BCL 公开)。

Joe Duffy 在his post 中提到:

“kernel32!SwitchToThread API 不会出现 Sleep(0) 和 Sleep(1) 会出现的问题。” (关于调度器的行为)

为什么 Sleep 的行为不像 SwitchToThread?为什么存在这种差异化,它有什么用? (如果有的话..)

【问题讨论】:

    标签: c# .net multithreading winapi sleep


    【解决方案1】:

    有两个不同之处。第一个在 SwitchToThread 的 MSDN 文档中提到:

    执行的收益仅限于调用线程的处理器。操作系统不会将执行切换到另一个处理器,即使该处理器处于空闲状态或正在运行优先级较低的线程。

    Sleep(0) 也允许其他处理器上的线程运行。

    SwitchToThread 也只屈服于单个线程调度上下文。另一方面,睡眠有多个等待的条件。 SleepEx 的文档详细说明了这一点:

    * An I/O completion callback function is called
    * An asynchronous procedure call (APC) is queued to the thread.
    * The time-out interval elapses
    

    这将产生多个线程。

    一般来说,Sleep(0) 更有可能产生时间片,并且总是让给操作系统,即使没有其他线程在等待。这就是为什么在循环中添加 Sleep(0) 会使处理器使用率从 100%(每个内核)降低到在许多情况下接近 0%。 SwitchToThread 不会,除非另一个线程正在等待一个时间片。

    【讨论】:

    • 根据您的分析,那么if (! SwitchToThread()) Sleep(0); 不是最好的解决方案吗?
    【解决方案2】:

    SwitchToThread() 是 Sleep(0) 的“更智能”版本。它没有很好的记录,但据我了解,它的工作方式如下:

    1. 当有其他线程处于ready 状态时(即,要运行的线程多于可用的逻辑处理器)并且这些线程的优先级与线程相同或更高调用 SwitchToThread(),它的行为与 与 Sleep(0) 相同 - 即将逻辑处理器让给这些线程之一,代价高昂的上下文切换;
    2. 当有线程处于ready 状态且具有较低优先级时,它就会退出,即调用 SwitchToThread() 的线程会继续执行,而不需要任何上下文切换或 3 来响铃0 次转换(它不会离开用户模式)——这与 Sleep(0) 的行为方式相反,它总是将控制权交给最低优先级的线程
    3. ready 状态下没有线程时,SwitchToThread() 也只是像 Sleep(0) 一样退出 - 因此,如果您在循环中执行此操作,您将获得 100% 的负载当前逻辑处理器,即烧掉电源。

    Sleep(1) 与 Sleep(0) 相同,但后面有 1 毫秒的延迟。这 1 毫秒的延迟释放了逻辑处理器并且不会消耗任何功率。相反,SwitchToThread 永远不会遇到任何延迟。

    所以最好将 SwitchToThread 与 Sleep(0) 进行比较,而不是与 Sleep(1) 进行比较,因为 Sleep(1) 与 Sleep(0) 相同 + 延迟 1 毫秒。

    我从“Intel 64 and IA-32 Architectures Optimization Reference Manual”和“Intel 64 and IA-32 Architectures Software Developer's Manual”中借用了一些关于这个问题的想法,其中倾向于调用一些pause CPU指令(如果您的等待时间很短,也可以通过 SwitchToThread() 或 Sleep(0) 作为内在函数使用。请注意,SwitchToThread() 或 Sleep(0) 几乎是立即的,而 Sleep(1) 至少持续一毫秒。

    还应考虑以下几点:

    • 对 Sleep() 或 SwitchToThread() 的每次调用都会经历昂贵的上下文切换成本,可能是 10000 多个周期
    • 它还承受从 ring 3 到 ring 0 转换的成本,可能是 1000 多个周期
    • 如果没有线程处于ready 状态,SwitchToThread() 或 Sleep(0) 可能没有用,但无论是否有其他线程处于“就绪”状态,Sleep(1) 都会等待至少一毫秒状态与否。

    如果您的等待循环往往很短,请考虑先执行一些pause CPU 指令。通过在 SwitchToThread() 或 Sleep() 调用之前使用一些 pause CPU 指令减慢“旋转等待”,多线程软件获得:

    • 通过促进等待任务从繁忙的等待中更轻松地获取资源来提高性能。
    • 通过在旋转时使用更少的管道部分来节省电力。
    • 消除了由 SwitchToThread() 或 Sleep(0) 或 Sleep(1) 调用的开销导致的绝大多数不必要执行的指令。

    但是,如果您要调用运行至少一毫秒的 Sleep(1),这在 CPU 周期方面非常长,那么您期望的等待周期会非常长,所以pause 指令在这种情况下将是徒劳的。

    当等待循环预计会持续很长时间时,最好通过调用操作系统同步 API 函数之一(例如 Windows 操作系统上的 WaitForSingleObject)让步给操作系统,而不是 SwitchToThread() 或 Sleep(0)或 Sleep(1),因为它们在长时间等待时非常浪费。此外,Sleep(1) 非常慢,并且诸如 WaitForSingleObject 或 EnterCriticalSection 之类的操作系统同步函数会反应更快,并且对资源更友好。

    我的结论:最好不要使用 Sleep(0) 或 Sleep(1) 或 SwitchToThread()。不惜一切代价避免“旋转等待”循环。使用WaitForMultipleObjects()、SetEvent()等高级同步函数——从性能、效率和省电的角度来看,它们是最好的。尽管它们还遭受昂贵的上下文切换和 ring 3 到 ring 0 的转换,但与您在使用 Sleep() 或 SwitchToThread() 的“自旋等待”循环中花费的费用相比,这些费用并不常见,而且非常合理。

    在支持 HT 技术的处理器上,自旋等待循环会占用处理器执行带宽的很大一部分。一个执行自旋等待循环的逻辑处理器会严重影响另一个逻辑处理器的性能。这就是为什么有时禁用 HT 可能会提高性能。

    持续轮询设备或文件或其他数据源的状态更改可能会导致计算机消耗更多电量,给内存和系统总线带来压力,并提供不必要的页面错误(使用 Windows 中的任务管理器查看哪些应用程序在空闲时产生最多的页面错误 - 这些是最低效的应用程序,因为它们使用“轮询”)。尽可能减少轮询并使用事件驱动的方式编写应用程序。这是我强烈推荐的最佳实践。您的应用程序应该一直处于睡眠状态,等待预先设置的多个事件。事件驱动应用程序的一个很好的例子是 Linux 下的 Nginx。以轮询电源更改为例。如果操作系统为各种设备状态更改提供通知服务(甚至是 WM_ 消息),例如将电源从 AC 转换为电池,请使用这些通知服务而不是轮询设备状态更改。这种方法减少了代码轮询电源状态的开销,因为当状态发生变化时,代码可以异步获取通知。

    与某些人所写的相反,Sleep(0) 不会将 CPU 消耗减少到接近于零。它将执行释放给处于“就绪”状态的其他线程,但如果没有这样的线程,它只会浪费数千个 CPU 周期并消耗当前线程的 100% CPU 周期,demonstrated by stackoverflow members 也是如此 - 我也刚刚重新检查了一遍 - Sleep(0) 循环在 Windows 10 64 位上消耗了当前线程 100% 的 CPU。

    【讨论】:

    • Sleep(0) 是一种特殊情况,它与 Sleep(1) 在 1 ms 延迟之间的差异要大得多。它使队列中的其他线程有机会运行,直到调度程序将其更新为再次运行。使用 Sleep(1) 时,它会显示“嘿调度程序,请在 1 毫秒后唤醒我”
    猜你喜欢
    • 2010-11-27
    • 2012-04-07
    • 1970-01-01
    • 2016-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多