13

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

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

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

4

8 回答 8

18

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

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

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

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

于 2009-06-02T02:51:05.137 回答
9

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

于 2009-06-02T02:53:53.087 回答
3

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

于 2009-06-02T03:52:27.003 回答
2

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

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

于 2009-06-02T03:01:17.097 回答
2

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

于 2009-06-02T03:03:13.473 回答
1

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

于 2009-06-02T03:15:49.983 回答
1

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

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

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

于 2009-06-02T03:21:18.897 回答
1

可以采取四种基本方法:

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

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

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

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

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

于 2012-05-15T15:17:18.203 回答