SwitchToThread与睡眠(1)

时间:2009-09-05 18:16:16

标签: c# .net multithreading winapi sleep

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

Joe Duffy在his post中提到:

  

“kernel32!SwitchToThread API没有出现Sleep(0)和Sleep(1)的问题。” (关于调度程序的行为)

为什么Sleep的行为与SwitchToThread完全不同?为什么存在这种差异,以及它有什么用呢? (如果有的话......)

2 个答案:

答案 0 :(得分:23)

有两点不同。第一个在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)更有可能产生一个时间片,并且即使没有其他线程在等待,它也总是屈服于OS。这就是为什么在循环中添加Sleep(0)会使处理器使用率从100%(每个核心)降至接近0%(在许多情况下)。除非另一个线程正在等待时间片,否则SwitchToThread不会。

答案 1 :(得分:2)

SwitchToThread()是一个更智能的"睡眠版本(0)。它没有很好的文档记录,但根据我的理解,它的工作方式如下:

  1. 当其他线程处于ready状态时(即,有多个线程想要运行而不是逻辑处理器可用)并且这些线程具有相同或更高的优先级在调用SwitchToThread()的线程中,它的行为与与Sleep(0)相同 - 即将逻辑处理器转移到其中一个线程,但代价是上下文切换成本高昂;
  2. 当线程处于ready状态且较低优先级时,它就会退出,即调用SwitchToThread()的线程继续执行而不需要任何上下文切换费用或3振铃0转换(它不会离开用户模式) - 这与相反,Sleep(0)的行为总是将控制权控制到最低优先级的线程;
  3. ready状态中没有线程时,SwitchToThread()也只是退出,如Sleep(0) - 所以如果你在循环中执行此操作,那么你只需要100当前逻辑处理器的负载百分比,即烧毁电源。
  4. 睡眠(1)与睡眠(0)相同,但延迟时间为1毫秒。这1毫秒的延迟释放了逻辑处理器,并且不会消耗任何功率。相反,SwitchToThread永远不会有任何延迟。

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

    我已经从英特尔64和IA-32架构优化参考手册"中借鉴了一些有关此问题的想法。和#34;英特尔64和IA-32架构软件开发人员手册",如果您的等待非常有利,可以通过SwitchToThread()或Sleep(0)调用一些pause CPU指令(也可用作内在函数)短。请注意,SwitchToThread()或Sleep(0)几乎是立即的,而Sleep(1)至少持续一毫秒。

    还应考虑以下因素:

    • 每次调用Sleep()或SwitchToThread()都会遇到上下文切换的昂贵代价,可能 10000+周期
    • 它还会受到第3环到第0环转换的成本,这可能 1000+周期
    • 如果没有线程处于ready状态,则SwitchToThread()或Sleep(0)可能没有用,但Sleep(1)等待至少一毫秒,无论其中是否有其他线程。准备'国家与否。

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

    • 通过促进等待任务从繁忙的等待中更容易获取资源的表现。
    • 通过在旋转时使用较少的管道部分来节省电力。
    • 消除由SwitchToThread()或Sleep(0)或Sleep(1)调用的开销引起的绝大多数不必要执行的指令。

    但是,如果你打算调用Sleep(1),它运行至少一毫秒,这在CPU周期方面是非常长的,那么你预计你的等待周期会非常长,所以{{1}在这种情况下,指令将是徒劳的。

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

    我的结论:最好不要使用Sleep(0)或Sleep(1)或SwitchToThread()。不惜一切代价避免“旋转等待”循环。使用WaitForMultipleObjects(),SetEvent()等高级同步函数 - 从性能,效率和省电方面来看,它们是最好的。虽然它们也遭受昂贵的上下文切换和响铃3到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。