Saturday, June 4, 2011

关于轮询,未来执行和并行执行

原文链接: Of polling, futures and parallel execution

现代计算的一个重要考虑因素是降低功耗。它对于移动设备(笔记本,平板,掌上电脑) 来说意义重大。你的新式CPU在空闲时会进入很多种低功耗状态。它空闲的时间越长, 低功耗状态越低,消耗的电能也越少,因此,你一次充电的电池使用时间就更长。

低功耗状态有一个死敌:轮询。可能只是为了读取内存地址来检测可能的改变,一个任务 会每隔一段时间唤醒CPU,CPU离开低功耗状态,唤醒他的所有内部结构,当你的间歇唤醒 进程完成它的任务很久之后CPU才能再次进入低功耗状态。这大大的削减了电池的寿命。 Intel公司自己 对此很焦虑

Python3.2带来了一个新的标准库模块来启动并行任务,并等待他们结束: concurrent.futures 模块 。 当我细读它的代码时,我发现它在一部分的worker线程和进程中使用了轮询。 我说“一些”是因为 ThreadPoolExecutorProcessPoolExecutor 的实现方式不同。 前者在它所有的worker线程中使用了轮询,而后者只在用来与worker进程通信的 队列管理线程中使用了轮询。

在这里使用轮询只是为了完成一个任务:检测是否应该启动关闭步骤。类似于queueing callables或者从以前的queued callable中获取结果等任务会通过同步队列对象来完成。 这些队列对象从threading或者multiprocessing模块中而来,具体是哪个取决于你所使用 的executor的实现方式。

因此,我想出了一个简单的 解决方法: 我用了一个sentinel来替换轮询,内置的sentinel是None。当一个队列接受到None时, 一个等待中的worker自然的被唤醒,并检查它是否应该关闭。对于ProcessPoolExecutor来说, 要多处理一个稍复杂的问题,因为我们需要唤醒除了那1个队列管理线程之外的N个worker进程。

在我最开始的补丁中,我还是会发生轮询超时;一个非常大的超时(10分钟),worker会在 那时被唤醒。这个大超时会存在是因为写的代码是有问题的,并且worker在接收到刚才提到 的sentinel时并不关闭。处于好奇,我深入到multiprocessing的源代码中,又发现了一个 很有趣的东西:在Windows平台, 使用非零非无穷超时的 multiprocessing.Queue.get() 使用了...轮询(为此,我提出了 issue 11668)。 它使用了很有趣的高频率轮询,因为它在开始的时候使用了1毫秒超时,并在每次循环中递增。

不用说它仍然使用了超时,而且是非常大的超时,这都会是我的补丁在Windows平台毫无作用, 因为超时的实现方式决定了每毫秒都可能被唤醒。因此,我硬着头皮移除了移除超时。我最新 的补丁完全没使用超时,因此在所有平台都不会引起间歇性唤醒。

Historically speaking, before Python 3.2, every timeout facility in the threading module, and therefore in much of multiprocessing since multiprocessing itself uses worker threads for various tasks, used polling. This was fixed in issue 7316. 从历史上来说,在Python3.2之前,threading模块中的所有超时工具,都使用了轮询。 因为multiprocessing模块使用了worker线程来完成各种任务,所以multiprocessing模块 中也存在同样的问题。这个问题在 issue 7316 解决。

No comments:

Post a Comment