【问题标题】:Is it better to poll or wait?轮询还是等待更好?
【发布时间】:2010-10-30 13:52:24
【问题描述】:

我看到了一个关于为什么“投票不好”的问题。就最小化一个线程使用的处理器时间而言,最好是进行自旋等待(即轮询 while 循环中所需的更改)还是等待内核对象(例如 Windows 中的内核事件对象) ?

对于上下文,假设代码需要在任何类型的处理器、单核、超线程、多核等上运行。还假设轮询或等待的线程在轮询结果令人满意之前无法继续如果它轮询而不是等待。最后,线程开始等待(或轮询)和满足条件之间的时间可能会很短,也可能很长。

由于操作系统在“等待”的情况下可能更有效地“轮询”,我不想看到“等待只是意味着其他人进行轮询”的说法,这是旧消息,不一定100% 准确。

【问题讨论】:

  • 感谢到目前为止所有回复的人。回复速度和质量都给我留下了深刻的印象。

标签: performance multithreading wait polling


【解决方案1】:

可以采取四种基本方法:

  1. 使用一些操作系统等待原语等待事件发生
  2. 使用一些操作系统计时器原语以某个定义的速率检查事件是否已经发生
  3. 反复检查事件是否已发生,但使用操作系统原语生成任意且未知持续时间的时间片,只要它没有发生。
  4. 反复检查事件是否发生,如果没有发生则不让出 CPU。

当 #1 可行时,它通常是最好的方法,除非延迟对事件的反应可能是有益的。例如,如果您希望在几秒钟内接收大量串口数据,并且如果在发送后 100 毫秒处理数据将与立即处理一样好,则使用后两者之一进行定期轮询方法可能比设置“收到数据”事件更好。

方法#3 相当粗糙,但在许多情况下可能是一个不错的方法。与方法 1 相比,它通常会浪费更多的 CPU 时间和资源,但在许多情况下实现起来会更简单,而且在许多情况下资源浪费会小到无所谓。

方法 #2 通常比 #3 更复杂,但其优点是能够使用单个计时器处理许多资源且无需专用线程。

方法#4 在嵌入式系统中有时是必要的,但通常非常糟糕,除非直接轮询硬件并且在相关事件发生之前不会有任何有用的事情可做。在许多情况下,等待的条件不可能发生,直到等待它的线程让出 CPU。在方法 #3 中让出 CPU 实际上会让等待线程比占用它更快地看到事件。

【讨论】:

  • @在给定的时间量内使用sleep()是否算作任意且未知的持续时间(#3)?
  • @PiotrDobrogost:我认为这基本上是#2。方法#3 相当于使用 sleep(0)。在某些嵌入式系统情况下,简单地使用“taskswitch”函数来执行无条件循环任务切换比设置更漂亮的作业调度方法以使任务不会被唤醒,如果它们只是将立即产生他们的“时间片”。
【解决方案2】:

我认为尚未提出的一点是,如果您的操作系统有很多工作要做,那么阻塞会将您的线程交给另一个进程。如果所有进程在它们应该使用的地方使用阻塞原语(例如内核等待、文件/网络 IO 等),那么您正在为内核提供更多信息来选择应该运行哪些线程。因此,它将在相同的时间内完成更多的工作。如果您的应用程序在等待该文件打开或数据包到达时可以做一些有用的事情,那么 yeilding 甚至可以帮助您成为自己的应用程序。

【讨论】:

    【解决方案3】:

    等待(阻塞)几乎总是最佳选择(“最佳”是指有效利用处理资源并最大限度地减少对同一系统上运行的其他代码的影响)。主要的例外是:

    1. 当预期的轮询持续时间很短(与阻塞系统调用的成本相似)时。
    2. 主要在嵌入式系统中,当 CPU 专用于执行特定任务并且 CPU 空闲没有任何好处时(例如,一些 90 年代后期构建的软件路由器使用了这种方法。)

    在 OS 内核中通常不使用轮询来实现阻塞系统调用 - 相反,事件(中断、计时器、互斥锁上的操作)会导致阻塞的进程或线程变为可运行。

    【讨论】:

    • 在 OS 内核中通常不使用轮询来实现阻塞系统调用 - 相反,事件(中断、定时器、互斥锁上的操作)会导致阻塞的进程或线程可运行 你的意思是一个线程可以在调度程序不知道的情况下由于一些中断、定时器等而变得可运行?我问是因为我很难相信。
    • 通过线程“可运行”,我的意思是准备好被安排运行。调度器仍然必须选择要执行的线程并在实际运行之前执行上下文切换。
    【解决方案4】:

    我同意 Darksquid 的观点,如果您的操作系统具有不错的并发原语,那么您不需要轮询。轮询通常出现在实时系统或没有操作系统的受限硬件上,然后您需要轮询,因为您可能没有等待()的选项,还因为它可以让您精确控制多长时间您希望在特定状态下等待,而不是受调度程序的支配。

    【讨论】:

      【解决方案5】:

      只有少数地方,通常是在操作系统低级事物(中断处理程序/设备驱动程序)中,旋转等待是有意义的/是必需的。通用应用程序总是最好等待一些同步原语,如互斥锁/条件变量/信号量。

      【讨论】:

        【解决方案6】:

        等待确实涉及更多资源,并且意味着额外的上下文切换。事实上,一些同步原语,如 CLR Monitors 和 Win32 临界区使用两阶段锁定协议——一些自旋等待在真正等待之前完成。

        我想做这两个阶段的事情会非常困难,并且会涉及大量的测试和研究。因此,除非您有时间和资源,否则请坚持使用 Windows 原语...他们已经为您进行了研究。

        【讨论】:

          【解决方案7】:

          等待是“更好”的行为方式。当您在等待内核对象时,您的线程将不会被授予任何 CPU 时间,因为调度程序知道没有准备好工作。只有在满足等待条件时,您的线程才会获得 CPU 时间。这意味着您不会不必要地占用 CPU 资源。

          【讨论】:

            【解决方案8】:

            如果操作系统对这些类型的并发原语有合理的实现,那么等待内核对象肯定会更好。

            除其他原因外,这让操作系统知道在等待的对象处于适当的状态之前,不要将有问题的线程调度为额外的时间片。否则,您的线程会不断被重新调度、上下文切换到,然后运行一段时间。

            您特别询问了如何最小化线程的处理器时间:在此示例中,内核对象上的线程阻塞将使用零时间;轮询线程会使用各种时间。

            此外,“其他人正在投票”的论点不必为真。当内核对象进入适当的状态时,内核可以在那个时刻查看哪些线程正在等待该对象......然后安排其中一个或多个执行。在这种情况下,内核(或其他任何人)无需轮询任何内容。

            【讨论】:

            • 当内核对象进入适当的状态时...() 如果不检查对象的状态,内核如何知道对象进入了适当的状态,这只是轮询?
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-04-20
            • 2012-03-27
            • 2019-12-04
            相关资源
            最近更新 更多