SwitchToThread与睡眠(1)

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

Joe Duffy在他的post中提到:

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

为什么Sleep不会像SwitchToThread一样? 为什么存在这种差异,以及它有什么用呢? (如果有的话……)

有两点不同。 第一个在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不会。

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

  1. 当其他线程处于ready状态时(即,有多个线程想运行而不是逻辑处理器可用)并且这些线程的优先级与调用SwitchToThread()的线程相同或更高 ,它的行为方式与hibernate(0) – 即以上下文切换的昂贵代价将逻辑处理器切换到这些线程之一;
  2. 当线程处于ready状态且优先级较低时 ,它就会退出,即调用SwitchToThread()的线程继续执行而不需要任何上下文切换或3到0的转换(它不会离开用户模式) – – 这与Sleep(0)的行为方式相反,它始终控制甚至是最低优先级的线程 ;
  3. 当没有处于ready状态的线程时,SwitchToThread()也会像Sleep(0)一样退出 – 所以如果你在循环中执行此操作,那么你只需要100%加载当前逻辑处理器,即烧毁电源。

睡眠(1)与睡眠(0)相同,但延迟时间为1毫秒。 这1毫秒的延迟释放了逻辑处理器,并且不会消耗任何功率。 相反,SwitchToThread从未经历任何延迟。

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

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

还应考虑以下因素:

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

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

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

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

当等待循环期望持续很长时间时,最好通过调用OS同步API函数之一(例如Windows操作系统上的WaitForSingleObject,而不是SwitchToThread()或Sleep(0)或Sleep()来操作系统。 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周期,这也是stackoverflow成员所certificate的 – 和我还重新检查了这一点 – Sleep(0)循环消耗了Windows 10 64位上当前线程的100%CPU。