作为上一篇文章”即使运行高优先级线程,低优先线程也能运行”的另一个反面例子,人们会认为调用 Sleep(0) 是一种放弃 CPU 时间片的简单方法。举个例子,如果消费者线程目前没有数据可以处理,则可以调用此函数来出让 CPU 时间片,以等待生产者线程产生需要的数据。
让我们回想一下,调度程序会查找优先级最高的可运行线程,如果存在平局,则所有候选项大致平均共享 CPU。线程可以调用 Sleep(0) 来放弃其时间片,从而减少其在 CPU 中的份额。但请注意,这并不能保证其他线程将运行。
如果存在具有最高优先级的唯一可运行线程,它可以调用 Sleep(0),并保持很长一段时间,但它不会放弃 CPU。这是因为 Sleep 的时间为零会释放分配给它的时间片,但使线程可运行。由于它是唯一具有最高优先级的可运行线程,因此它会立即恢复 CPU。如果没有其他人排队,你实际上并没有将时间片出让给其他线程。
因此,如果使用 Sleep(0) 作为不那么有效的出让 CPU 的方法,它永远不会允许运行优先级较低的线程。这意味着各种后台活动(如索引)永远不会有机会运行,因为你的程序占用了所有 CPU 时间。更重要的是,你的程序从未真正释放 CPU 的事实,意味着计算机永远不会进入低功耗状态。笔记本电脑会更快地耗尽电池电量并运行得更热,终端服务器将无休止地消耗 CPU 时间片。
最好的办法是等待正确的同步对象,以便线程进入睡眠状态,直到有工作要做。如果你由于某种原因不能这样做,至少 Sleep 一个非零的时间值。这样,在短暂的时刻,你的线程不可运行,而其他线程(包括优先级较低的线程) 有机会运行。(这也将在一定程度上降低功耗,尽管不如等待正确的同步对象那么多。)
总结
我们需要了解 CPU 时间片在各个线程上的调度原理,Sleep(0) 和 Sleep(1) 虽然只是参数上的不同,但底层运行逻辑却有很大的区别。
由于程序可能会运行在各种不同配置的系统上,简单的使用 Sleep 来进行线程同步,将产生预料不到的偏差,你绝对不希望这样。
所以,正如原文作者指出的,多线程下的线程协调工作,还是使用内核同步对象比较好。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Consequences of the scheduling algorithm: Sleeping doesn’t always help》