第一:你真的需要一个后台线程来闲置吗?
在大多数平台上,启动一个新线程很便宜。(除了在 Windows 和 Linux 上,它非常便宜。)那么,为什么不在你需要的时候启动一个线程呢?(保持一个线程列表和单个线程一样容易,对吧?)
或者,为什么不创建一个ThreadPoolExecutor
,然后向它提交作业,让执行者担心它们何时运行以及在哪个线程上运行。任何时候你只要考虑“需要在不阻塞主线程的情况下运行的任务”而不是“需要等待工作的工作线程”,你就会让你的生活更轻松。在幕后,仍然有一个或多个工作线程在队列上等待,或者类似的东西,但那部分都是为您编写的(并已调试和优化)。您只需要编写任务,它们只是常规功能。
但是,如果你想写显式的后台线程,你可以,所以我会解释。
如何在不占用 CPU 时间的情况下让工作线程空闲?... 向工作线程发出信号以执行某项操作的最佳方式是什么?
在值准备好之前使线程空闲的方法是等待同步对象。在任何现代操作系统上,等待同步对象意味着操作系统停止为您提供任何 CPU 时间,直到对象为您准备好。*
您可以在模块文档中看到各种不同的选项Threading
,但在大多数情况下使用的显而易见的选项是Condition
. 向工作线程发出信号的方法是notify
向Condition
.
但是,通常 aQueue
要简单得多。要等待 a Queue
,只需使用 调用它的get
方法block=True
。要通知另一个线程唤醒,put
只需Queue
. (在幕后, aQueue
包装了一个list
ordeque
或其他集合, aLock
和 a Condition
,所以你只需告诉它你想要做什么——检查一个值,阻塞直到有一个值,添加一个值——而不是处理等待并发出信号并保护收藏。)
请参阅使用线程控制 wxPython 中的 UI 元素的答案,了解如何在两个方向上发出信号,从工作线程到 UI 线程,反之亦然。
我有一些方法让工作线程向 UI 本身注册回调,以便当单击按钮或发生任何其他 UI 事件时,工作线程会收到信号以更改其正在执行的操作。
如果你愿意,你可以这样做。只需传递self.queue.put
或def callback(value): self.value = value; self.condition.notify()
作为回调,GUI 线程甚至不必知道回调正在触发另一个线程。
事实上,这是一个相当不错的设计,当您决定在内联和后台线程之间来回移动一些代码时,或者将其移至子进程而不是后台线程或其他时,它可能会让您非常高兴。
我现在无法想象这一点,但我可以看到随着应用程序变得更加复杂,还必须在工作线程实际上忙于做某事时向它发出信号。
但是如果它很忙,你想发生什么?
如果您只想说“如果您空闲,请醒来并执行此任务;否则,请坚持并在您准备好时执行此任务”,这正是 aQueue
或 anExecutor
自动为您做的事情。
如果你想说,“如果你闲着,就醒来,否则,别担心”,这就是 a Condition
orEvent
会做的事情。
如果您想说“如果您空闲,请醒来并执行此操作,否则,请取消您正在执行的操作并改为执行此操作”,这有点复杂。您几乎需要让后台线程在忙碌时定期检查“interrupt_me”变量(并在其Lock
周围放置一个),然后您将设置该标志并通知Condition
...尽管在某些情况下,您可以合并空闲和忙碌情况合并为一个Condition
或Event
(空闲时调用无限,忙碌时wait()
快速检查wait(timeout=0)
)。
* 在某些情况下——例如,Linuxfutex
或CriticalSection
Windows——在某些情况下,它实际上可能会占用一点 CPU 时间,因为这恰好是一个很好的优化。但关键是,在您准备好使用它之前,您不会要求任何 CPU 时间。